const API_BASE_URL = 'http://164.132.203.174:3004'; // Replace with your actual API base URL function getHeaders(cookie, url) { const domain = extractDomain(url); const countryCode = domain.split('.').pop(); const languageMap = { 'fr': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', 'de': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7', 'es': 'es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7', 'uk': 'en-GB,en;q=0.9,en-US;q=0.8' }; const language = languageMap[countryCode] || languageMap['uk']; // Default to UK if unknown return { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "Accept-Language": language, "Cache-Control": "no-cache", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", "X-Requested-With": "XMLHttpRequest", "Referer": `https://www.amazon.${countryCode}/`, "Cookie": cookie }; } function extractDomain(url) { // Remove protocol and get domain const domain = url.replace(/(^\w+:|^)\/\//, '').split('/')[0]; // Handle cases like 'www.amazon.co.uk' const parts = domain.split('.'); if (parts.length > 2) { return parts.slice(-3).join('.'); } return domain; } function extractPriceAndPrimeEligibility(content) { let mainPrice = null; let daodiPrice = null; let isPrimeEligible = false; let isDaodiPrime = false; try { const $ = Cheerio.load(content); // Extract main price const mainPriceElement = $('#corePrice_feature_div .a-price[data-a-color="base"] .a-offscreen').first(); if (mainPriceElement.length) { let priceText = mainPriceElement.text().trim(); mainPrice = parseFloat(cleanPriceString(priceText)); } // Check Prime eligibility for main price (using the previous method) const fulfillerInfo = $('.offer-display-feature-text[offer-display-feature-name="desktop-fulfiller-info"] .offer-display-feature-text-message'); if (fulfillerInfo.length && fulfillerInfo.text().trim().toLowerCase().includes('amazon')) { isPrimeEligible = true; } // Extract daodi price and check for "prime" const daodiElement = $('.daodi-content'); if (daodiElement.length) { const daodiPriceElement = daodiElement.find('.a-price .a-offscreen').first(); if (daodiPriceElement.length) { let priceText = daodiPriceElement.text().trim(); daodiPrice = parseFloat(cleanPriceString(priceText)); } // Check for "prime" (case-insensitive) in daodi content const daodiContent = daodiElement.text().toLowerCase(); isDaodiPrime = daodiContent.includes('france'); } } catch (e) { //console.log('Price and Prime eligibility extraction failed: ' + e.message); } //console.log(`Extracted data - Main Price: ${mainPrice}, Main Prime Eligible: ${isPrimeEligible}, Daodi Price: ${daodiPrice}, Daodi Prime: ${isDaodiPrime}`); return { mainPrice: mainPrice !== null ? mainPrice.toFixed(2) : null, daodiPrice: daodiPrice !== null ? daodiPrice.toFixed(2) : null, isPrimeEligible, isDaodiPrime }; } function cleanPriceString(priceText) { // Remove currency symbols and non-breaking spaces priceText = priceText.replace(/[^\d,\.]/g, ''); // Replace comma with dot if comma is used as decimal separator if (priceText.indexOf(',') > -1 && priceText.indexOf('.') === -1) { priceText = priceText.replace(',', '.'); } else { // Remove thousand separators priceText = priceText.replace(/,/g, ''); } return priceText; } function fetchWithRetry(request, maxRetries = 3) { let retries = 0; while (retries < maxRetries) { try { const response = UrlFetchApp.fetch(request.url, { method: request.method, headers: request.headers, muteHttpExceptions: true, followRedirects: true, validateHttpsCertificates: false }); if (response.getResponseCode() === 200) { return response; } } catch (error) { console.error(`Error fetching ${request.url}: ${error}`); } retries++; if (retries < maxRetries) { //Utilities.sleep(1000 * retries); // Exponential backoff } } console.error(`Failed to fetch ${request.url} after ${maxRetries} attempts`); return null; } function checkProducts(products, cookie) { console.log(`Checking ${products.length} products...`); const checkedProducts = []; for (let product of products) { const request = { url: product.url, method: 'get', headers: getHeaders(cookie, product.url) }; const response = fetchWithRetry(request); if (response) { const content = response.getContentText(); const { mainPrice, daodiPrice, isPrimeEligible, isDaodiPrime } = extractPriceAndPrimeEligibility(content); const isInStock = (mainPrice !== null && parseFloat(mainPrice) > 0) || (daodiPrice !== null && parseFloat(daodiPrice) > 0); const result = { url: request.url, isInStock, mainPrice, daodiPrice, isPrimeEligible, isDaodiPrime }; checkedProducts.push(result); console.log(`Checked product: ${JSON.stringify(result)}`); } } console.log(`Successfully checked ${checkedProducts.length} products.`); return checkedProducts; } function getCookie() { const cookieUrl = `${API_BASE_URL}/config/cookie`; const response = UrlFetchApp.fetch(cookieUrl, { method: 'get', muteHttpExceptions: true, followRedirects: true, validateHttpsCertificates: false }); return JSON.parse(response.getContentText()).value; } function getProductsToCheck() { const url = `${API_BASE_URL}/products/random`; const response = UrlFetchApp.fetch(url, { method: 'get', muteHttpExceptions: true, followRedirects: true, validateHttpsCertificates: false }); const products = JSON.parse(response.getContentText()); //console.log(`Retrieved ${products.length} products to check:`); //products.forEach(product => console.log(`- ${product.url}`)); return products; } function updateProductStatuses(products) { const BATCH_SIZE = 500; const url = `${API_BASE_URL}/products/prices`; // Filter out non-Prime products const primeProducts = products.filter(product => product.isPrimeEligible || product.isDaodiPrime); console.log(`Processing ${primeProducts.length} Prime-eligible products...`); for (let i = 0; i < primeProducts.length; i += BATCH_SIZE) { const productBatch = primeProducts.slice(i, i + BATCH_SIZE); const formattedProducts = productBatch.map(product => { let price = null; if (product.isPrimeEligible && product.mainPrice && !isNaN(parseFloat(product.mainPrice))) { price = parseFloat(product.mainPrice); } if (product.isDaodiPrime && product.daodiPrice && !isNaN(parseFloat(product.daodiPrice))) { price = price === null ? parseFloat(product.daodiPrice) : Math.min(price, parseFloat(product.daodiPrice)); } if (price !== null) { return { url: product.url, price: price.toFixed(2) }; } return null; }).filter(product => product !== null); console.log(`Sending batch ${i / BATCH_SIZE + 1} (${formattedProducts.length} products with valid prices)`); if (formattedProducts.length > 0) { const response = UrlFetchApp.fetch(url, { method: 'post', contentType: 'application/json', payload: JSON.stringify({ products: formattedProducts }), muteHttpExceptions: true }); if (response.getResponseCode() === 200) { console.log(`Successfully updated batch ${i / BATCH_SIZE + 1}`); } else { console.error(`Failed to send product statuses for batch ${i / BATCH_SIZE + 1}. Response code: ${response.getResponseCode()}`); } } else { console.log(`Skipping batch ${i / BATCH_SIZE + 1} as it contains no products with valid prices`); } } console.log(`Finished processing product statuses.`); } function main() { console.log("Starting main function..."); const cookie = getCookie(); console.log("Retrieved cookie."); const productsToCheck = getProductsToCheck(); console.log(`Retrieved ${productsToCheck.length} products to check.`); const checkedProducts = checkProducts(productsToCheck, cookie); console.log(`Checked ${checkedProducts.length} products.`); const primeProducts = checkedProducts.filter(product => (product.isPrimeEligible || product.isDaodiPrime) && product.isInStock ); console.log(`Filtered ${primeProducts.length} in-stock and Prime-eligible products.`); if (primeProducts.length > 0) { updateProductStatuses(primeProducts); } else { console.log("No Prime-eligible products to update."); } console.log("Main function completed."); } main()