Skip to content

Instantly share code, notes, and snippets.

@prehensilecode
Forked from lfhbento/userscript.js
Last active February 21, 2025 20:02
Show Gist options
  • Save prehensilecode/fbd2d77ec8bd69c8381412bfbab0dfef to your computer and use it in GitHub Desktop.
Save prehensilecode/fbd2d77ec8bd69c8381412bfbab0dfef 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/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