Goodreads Amazon UK Price Checker

Shows Amazon UK prices for books in your Goodreads To Read list

Size

9.0 KB

Version

1.1.1

Created

Oct 16, 2025

Updated

7 days ago

1// ==UserScript==
2// @name		Goodreads Amazon UK Price Checker
3// @description		Shows Amazon UK prices for books in your Goodreads To Read list
4// @version		1.1.1
5// @match		https://www.goodreads.com/review/list/*
6// @icon		https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico
7// @grant		GM.xmlhttpRequest
8// @connect		amazon.co.uk
9// ==/UserScript==
10(function() {
11    'use strict';
12
13    console.log('Goodreads Amazon UK Price Checker started');
14
15    // Add styles for price display
16    const styles = `
17        .amazon-price-container {
18            margin-top: 5px;
19            font-size: 12px;
20        }
21        .amazon-price {
22            display: inline-block;
23            padding: 4px 8px;
24            background-color: #ff9900;
25            color: #111;
26            border-radius: 3px;
27            font-weight: bold;
28            text-decoration: none;
29            margin-right: 5px;
30        }
31        .amazon-price:hover {
32            background-color: #ffad33;
33        }
34        .amazon-loading {
35            color: #999;
36            font-style: italic;
37        }
38        .amazon-error {
39            color: #c00;
40            font-size: 11px;
41        }
42        .amazon-not-found {
43            color: #666;
44            font-size: 11px;
45        }
46    `;
47    
48    TM_addStyle(styles);
49
50    // Function to extract price from Amazon UK page
51    function extractPriceFromAmazonPage(html) {
52        console.log('Extracting price from Amazon page');
53        
54        // Try to find the actual selling price (not RRP)
55        // First try: Look for the main price display with a-price-whole
56        const priceWholeMatch = html.match(/<span class="a-price-whole">([^<]+)<\/span>/);
57        if (priceWholeMatch) {
58            let price = priceWholeMatch[1].replace(',', '').replace(/\.$/, '');
59            console.log('Found price from a-price-whole:', price);
60            return parseFloat(price);
61        }
62        
63        // Second try: Look for price with data-a-color="base" (actual selling price, not RRP)
64        // This regex looks for a-price elements with data-a-color="base" and extracts the offscreen price
65        const basePriceRegex = /<span class="a-price"[^>]*data-a-color="base"[^>]*>[\s\S]*?<span class="a-offscreen">£([^<]+)<\/span>/;
66        const basePriceMatch = html.match(basePriceRegex);
67        if (basePriceMatch) {
68            const price = parseFloat(basePriceMatch[1].replace(',', ''));
69            console.log('Found price from base color price:', price);
70            return price;
71        }
72        
73        // Third try: Look for a-offscreen prices (but skip RRP and strikethrough ones)
74        const offscreenMatches = html.matchAll(/<span class="a-price"[^>]*>[\s\S]*?<span class="a-offscreen">£([^<]+)<\/span>/g);
75        const prices = [];
76        for (const match of offscreenMatches) {
77            const fullMatch = match[0];
78            const priceText = match[1];
79            
80            // Skip if it has strike-through or is marked as secondary color (RRP)
81            if (fullMatch.includes('data-a-strike="true"') || 
82                fullMatch.includes('data-a-color="secondary"') ||
83                priceText.includes('RRP')) {
84                continue;
85            }
86            
87            const price = parseFloat(priceText.replace(',', ''));
88            if (!isNaN(price) && price > 0) {
89                prices.push(price);
90            }
91        }
92        
93        // Return the first valid price found (usually the selling price)
94        if (prices.length > 0) {
95            console.log('Found price from filtered offscreen:', prices[0]);
96            return prices[0];
97        }
98        
99        // Fourth try: Generic pound sign pattern
100        const genericMatch = html.match(/£(\d+\.?\d*)/);
101        if (genericMatch) {
102            const price = parseFloat(genericMatch[1].replace(',', ''));
103            console.log('Found price from generic pattern:', price);
104            return price;
105        }
106        
107        console.log('No price found in page');
108        return null;
109    }
110
111    // Function to search Amazon UK for a book
112    async function searchAmazonUK(title, author) {
113        console.log(`Searching Amazon UK for: ${title} by ${author}`);
114        
115        const searchQuery = encodeURIComponent(`${title} ${author}`);
116        const searchUrl = `https://www.amazon.co.uk/s?k=${searchQuery}&i=stripbooks`;
117        
118        try {
119            const response = await GM.xmlhttpRequest({
120                method: 'GET',
121                url: searchUrl,
122                headers: {
123                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
124                }
125            });
126            
127            if (response.status !== 200) {
128                console.error('Amazon search failed with status:', response.status);
129                return null;
130            }
131            
132            const html = response.responseText;
133            
134            // Extract first product link
135            const productLinkMatch = html.match(/href="(\/[^"]*\/dp\/[A-Z0-9]{10}[^"]*)"/);
136            if (!productLinkMatch) {
137                console.log('No product found in search results');
138                return null;
139            }
140            
141            let productUrl = productLinkMatch[1];
142            // Clean up the URL
143            productUrl = productUrl.split('/ref=')[0];
144            productUrl = `https://www.amazon.co.uk${productUrl}`;
145            
146            console.log('Found product URL:', productUrl);
147            
148            // Fetch the product page to get the price
149            const productResponse = await GM.xmlhttpRequest({
150                method: 'GET',
151                url: productUrl,
152                headers: {
153                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
154                }
155            });
156            
157            if (productResponse.status !== 200) {
158                console.error('Product page fetch failed with status:', productResponse.status);
159                return null;
160            }
161            
162            const price = extractPriceFromAmazonPage(productResponse.responseText);
163            
164            if (price) {
165                return {
166                    price: price,
167                    url: productUrl
168                };
169            }
170            
171            return null;
172            
173        } catch (error) {
174            console.error('Error searching Amazon:', error);
175            return null;
176        }
177    }
178
179    // Function to add price to a book row
180    async function addPriceToBook(bookRow) {
181        const titleElement = bookRow.querySelector('td.field.title a');
182        const authorElement = bookRow.querySelector('td.field.author a');
183        
184        if (!titleElement || !authorElement) {
185            console.log('Could not find title or author for book row');
186            return;
187        }
188        
189        const title = titleElement.textContent.trim();
190        const author = authorElement.textContent.trim();
191        
192        console.log(`Processing book: ${title} by ${author}`);
193        
194        // Create container for price
195        const priceContainer = document.createElement('div');
196        priceContainer.className = 'amazon-price-container';
197        priceContainer.innerHTML = '<span class="amazon-loading">Checking Amazon UK...</span>';
198        
199        // Insert after the title
200        const titleCell = bookRow.querySelector('td.field.title .value');
201        titleCell.appendChild(priceContainer);
202        
203        // Search Amazon
204        const result = await searchAmazonUK(title, author);
205        
206        if (result && result.price) {
207            priceContainer.innerHTML = `
208                <a href="${result.url}" target="_blank" class="amazon-price">
209                    £${result.price.toFixed(2)}
210                </a>
211                <span style="font-size: 11px; color: #666;">on Amazon UK</span>
212            `;
213        } else {
214            priceContainer.innerHTML = '<span class="amazon-not-found">Price not found on Amazon UK</span>';
215        }
216    }
217
218    // Function to process all books with delay to avoid rate limiting
219    async function processAllBooks() {
220        const bookRows = document.querySelectorAll('tr.bookalike.review');
221        console.log(`Found ${bookRows.length} books to process`);
222        
223        for (let i = 0; i < bookRows.length; i++) {
224            await addPriceToBook(bookRows[i]);
225            // Add delay between requests to avoid overwhelming Amazon
226            if (i < bookRows.length - 1) {
227                await new Promise(resolve => setTimeout(resolve, 2000));
228            }
229        }
230        
231        console.log('Finished processing all books');
232    }
233
234    // Initialize when page is ready
235    function init() {
236        console.log('Initializing Amazon UK price checker');
237        
238        // Wait a bit for the page to fully load
239        setTimeout(() => {
240            processAllBooks();
241        }, 1000);
242    }
243
244    // Start the extension
245    if (document.readyState === 'loading') {
246        document.addEventListener('DOMContentLoaded', init);
247    } else {
248        init();
249    }
250
251})();
Goodreads Amazon UK Price Checker | Robomonkey