diff --git a/book-links.user.js b/book-links.user.js index 05258da..ac73de3 100644 --- a/book-links.user.js +++ b/book-links.user.js @@ -12,9 +12,12 @@ // @match https://www.amazon.com/*/dp/* // @connect annas-archive.gl // @connect www.waterstones.com +// @connect www.chez-mon-libraire.fr // @updateURL https://raw.githubusercontent.com/alexture/book-links/main/book-links.user.js // @downloadURL https://raw.githubusercontent.com/alexture/book-links/main/book-links.user.js // @grant GM_xmlhttpRequest +// @grant GM_getValue +// @grant GM_setValue // ==/UserScript== (function () { @@ -136,8 +139,20 @@ }); } - async function checkAnna(isbn, title, author) { - const query = (title && author) ? `${title} ${author}` : isbn; + const CACHE_TTL_MS = 14 * 24 * 60 * 60 * 1000; // 14 days + + async function withCache(key, fn) { + const entry = GM_getValue(key); + if (entry && Date.now() - entry.ts < CACHE_TTL_MS) return entry.value; + + const value = await fn(); + // Only cache confident answers — retry uncertain ones (network error, + // timeout, JS-rendered page) next visit instead of freezing them in. + if (value.found !== null) GM_setValue(key, { value, ts: Date.now() }); + return value; + } + + async function checkAnnaQuery(query) { const url = `https://annas-archive.gl/search?q=${encodeURIComponent(query)}`; const res = await gmFetch(url); if (!res.ok) return { found: null, url }; @@ -152,8 +167,21 @@ return { found: null, url }; } - async function checkWaterstones(isbn) { - const searchUrl = `https://www.waterstones.com/books/search/term/${isbn}`; + async function checkAnna(isbn, title, author) { + if (isbn) { + const byIsbn = await checkAnnaQuery(isbn); + // A specific edition's ISBN may be unindexed even though the book exists + // under another edition — retry by title/author before calling it not found. + if (byIsbn.found === false && title && author) { + return checkAnnaQuery(`${title} ${author}`); + } + return byIsbn; + } + return checkAnnaQuery(`${title} ${author}`); + } + + async function checkWaterstonesQuery(query) { + const searchUrl = `https://www.waterstones.com/books/search/term/${encodeURIComponent(query)}`; const res = await gmFetch(searchUrl); if (!res.ok) return { found: null, url: searchUrl }; @@ -172,6 +200,36 @@ return { found: null, url: searchUrl }; } + async function checkWaterstones(isbn, title, author) { + const byIsbn = await checkWaterstonesQuery(isbn); + if (byIsbn.found === false && title && author) { + return checkWaterstonesQuery(`${title} ${author}`); + } + return byIsbn; + } + + async function checkChezMonLibraireQuery(query) { + const searchUrl = `https://www.chez-mon-libraire.fr/listeliv.php?base=allbooks&mots_recherche=${encodeURIComponent(query)}`; + const res = await gmFetch(searchUrl); + if (!res.ok) return { found: null, url: searchUrl }; + + const noResults = /n.avons pas trouv/i.test(res.text); + if (noResults) return { found: false, url: searchUrl }; + + const hasResults = /\/livre\/\d/.test(res.text) || /produit[s]?\s+trouv/i.test(res.text); + if (hasResults) return { found: true, url: searchUrl }; + + return { found: null, url: searchUrl }; + } + + async function checkChezMonLibraire(isbn, title, author) { + const byIsbn = await checkChezMonLibraireQuery(isbn); + if (byIsbn.found === false && title && author) { + return checkChezMonLibraireQuery(`${title} ${author}`); + } + return byIsbn; + } + function renderRow(label, result) { if (result.found === true) { return `
${label}
`; @@ -232,15 +290,26 @@ widget.querySelector('#__bl-results').textContent = 'Checking…'; const isWaterstones = window.location.hostname.includes('waterstones.com'); - const checksToRun = [checkAnna(isbn, title, author)]; - if (!isWaterstones && isbn) checksToRun.push(checkWaterstones(isbn)); + const cacheKeyFor = (site) => `${site}:${isbn || `${title}|${author}`}`; + const checksToRun = [ + withCache(cacheKeyFor('anna'), () => checkAnna(isbn, title, author)), + ]; + if (!isWaterstones && isbn) { + checksToRun.push(withCache(cacheKeyFor('waterstones'), () => checkWaterstones(isbn, title, author))); + } + if (isbn) { + checksToRun.push(withCache(cacheKeyFor('chezmonlibraire'), () => checkChezMonLibraire(isbn, title, author))); + } const results = await Promise.all(checksToRun); - const annaResult = results[0]; - const waterstonesResult = isWaterstones ? null : results[1]; + let idx = 0; + const annaResult = results[idx++]; + const waterstonesResult = (!isWaterstones && isbn) ? results[idx++] : null; + const chezMonLibraireResult = isbn ? results[idx++] : null; let html = ''; - if (!isWaterstones) html += renderRow('Waterstones', waterstonesResult); + if (waterstonesResult) html += renderRow('Waterstones', waterstonesResult); + if (chezMonLibraireResult) html += renderRow('Chez Mon Libraire', chezMonLibraireResult); html += renderRow("Anna's Archive", annaResult); widget.querySelector('#__bl-results').innerHTML = html;