Add Chez Mon Libraire, ISBN-first search with title fallback, and result caching
Anna's Archive and Waterstones now search by ISBN first and only fall back to title/author when that comes back with an explicit no-results, since a specific edition's ISBN can be unindexed even though the book exists under another edition. Chez Mon Libraire is added as a third source (isbn-based search, French no-results/found text patterns). Results are cached per site+isbn via GM_getValue/GM_setValue for 14 days to cut down on repeat requests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f1cf418f63
commit
9117841bb1
1 changed files with 78 additions and 9 deletions
|
|
@ -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 `<div>✓ <a href="${result.url}" target="_blank" style="color:#7eb8f7;text-decoration:none">${label}</a></div>`;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue