From 622a2045e570ac504133268fe47b3a7dfb580b1a Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 14 Oct 2025 10:36:08 -0600 Subject: [PATCH] Make extension auto grab book id --- index.js | 37 ++++++++++++++++++ manifest.json | 3 +- safaribooks/downloader.js | 79 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 077704f..d2187d4 100644 --- a/index.js +++ b/index.js @@ -23,6 +23,41 @@ function setDisabled(disabled) { kindleCheckbox.disabled = disabled; } +function extractBookIdFromUrl(url) { + try { + const { pathname } = new URL(url); + const segments = pathname.split("/").filter(Boolean).reverse(); + return segments.find((segment) => /^[0-9]{9,}$/.test(segment)) || null; + } catch (_) { + return null; + } +} + +async function detectActiveBook() { + if (!chrome.tabs?.query) { + return; + } + + try { + const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (!activeTab?.url || !activeTab.url.includes("/library/view/")) { + return; + } + + const detectedBookId = extractBookIdFromUrl(activeTab.url); + if (!detectedBookId) { + return; + } + + if (bookIdInput.value !== detectedBookId) { + bookIdInput.value = detectedBookId; + appendLog(`Detected book ID ${detectedBookId} from the active tab.`); + } + } catch (error) { + console.warn("SafariBooks Downloader: unable to detect active book.", error); + } +} + form.addEventListener("submit", async (event) => { event.preventDefault(); const bookId = bookIdInput.value.trim(); @@ -48,3 +83,5 @@ form.addEventListener("submit", async (event) => { setDisabled(false); } }); + +detectActiveBook(); diff --git a/manifest.json b/manifest.json index 7098b4d..2b008b2 100644 --- a/manifest.json +++ b/manifest.json @@ -32,7 +32,8 @@ "permissions": [ "downloads", "cookies", - "storage" + "storage", + "tabs" ], "host_permissions": [ "https://learning.oreilly.com/*", diff --git a/safaribooks/downloader.js b/safaribooks/downloader.js index fe785a4..75e4f78 100644 --- a/safaribooks/downloader.js +++ b/safaribooks/downloader.js @@ -539,14 +539,83 @@ export class SafariBooksDownloader { async fetchImages() { for (const [relative, url] of this.imageDownloads.entries()) { - const response = await fetch(url, { credentials: "include" }); - if (!response.ok) { - this.log(`Warning: unable to fetch image ${url} (status ${response.status})`); + const result = await this.fetchImageWithFallback(relative, url); + if (result) { + this.imageDownloads.set(relative, result); + } + } + } + + async fetchImageWithFallback(relative, primaryUrl) { + const attempts = []; + const tried = new Set(); + + const queue = [primaryUrl, ...this.buildImageFallbackUrls(relative, primaryUrl)]; + for (const candidate of queue) { + if (!candidate || tried.has(candidate)) { continue; } - const data = new Uint8Array(await response.arrayBuffer()); - this.imageDownloads.set(relative, { data, sourceUrl: url }); + tried.add(candidate); + try { + const response = await fetch(candidate, { credentials: "include" }); + if (!response.ok) { + attempts.push(`${candidate} (status ${response.status})`); + continue; + } + const data = new Uint8Array(await response.arrayBuffer()); + if (candidate !== primaryUrl) { + this.log(`Info: fetched image ${relative} via fallback ${candidate}`); + } + return { data, sourceUrl: candidate }; + } catch (error) { + attempts.push(`${candidate} (${error.message})`); + } } + + if (attempts.length) { + this.log( + `Warning: unable to fetch image ${primaryUrl}. Attempts: ${attempts.join("; ")}` + ); + } + return null; + } + + buildImageFallbackUrls(relative, url) { + const candidates = new Set(); + + if (url.includes("/Images/")) { + candidates.add(url.replace("/Images/", "/images/")); + } + if (url.includes("/images/")) { + candidates.add(url.replace("/images/", "/Images/")); + } + + if (url.includes("/files/Images/")) { + const base = url.replace(/Images\/[^/]*$/i, "Images/"); + ["cover.jpg", "cover.jpeg", "cover.png", "cover-large.jpg"].forEach((file) => { + candidates.add(`${base}${file}`); + }); + } + + const isbnMatch = url.match(/Images\/(\d{10,13})\.(jpg|jpeg|png|gif)$/i); + if (isbnMatch) { + const isbn = isbnMatch[1]; + const sizes = ["600w", "400w", "250w", ""]; + sizes.forEach((size) => { + const trimmedSize = size ? `/${size}` : ""; + candidates.add(`https://learning.oreilly.com/library/cover/${isbn}${trimmedSize}/`); + }); + } + + if (this.bookInfo?.cover && url !== this.bookInfo.cover) { + candidates.add(this.bookInfo.cover); + } + + if (relative && this.bookInfo?.cover_url) { + candidates.add(this.bookInfo.cover_url); + } + + return Array.from(candidates).filter(Boolean); } async fetchCssImages() {