Skip to main content
Glama
cart.ts10.4 kB
import * as cheerio from 'cheerio' import fs from 'fs' import { USE_MOCKS, EXPORT_LIVE_SCRAPING_FOR_MOCKS, getAmazonDomain } from './config.js' import { createBrowserAndPage, getTimestamp, throwIfNotLoggedIn } from './utils.js' import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // ################################## // Cart Content Types // ################################## interface CartItem { title: string price: string quantity: number image?: string productUrl?: string asin?: string availability: string isSelected: boolean } interface CartContent { isEmpty: boolean items: CartItem[] subtotal?: string totalItems?: number } // ################################## // Get Cart Content // ################################## export async function getCartContent(): Promise<CartContent> { let html: string if (USE_MOCKS) { console.error('[INFO][get-cart-content] Fetching cart content from mocks') const mockPath = `${__dirname}/../mocks/getCartContent.html` html = fs.readFileSync(mockPath, 'utf-8') } else { const domain = getAmazonDomain() const url = `https://www.${domain}/-/en/gp/cart/view.html?ref_=nav_cart` console.error(`[INFO][get-cart-content] Fetching cart content from ${url}`) const { browser, page } = await createBrowserAndPage() try { // Navigate to the cart page await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }) // Handle login if needed await throwIfNotLoggedIn(page) // Wait for the cart content to load try { await page.waitForSelector('#sc-active-cart', { timeout: 10000 }) } catch (e) { throw new Error('[INFO][get-cart-content] Could not find cart container. Ensure you are logged in and the cart is accessible.') } if (EXPORT_LIVE_SCRAPING_FOR_MOCKS) { // Export only the `#sc-active-cart` content to a mock file const timestamp = getTimestamp() const mockPath = `${__dirname}/../mocks/getCartContent_${timestamp}.html` const cartHtml = await page.$eval('#sc-active-cart', el => el.outerHTML) fs.writeFileSync(mockPath, cartHtml) console.error(`[INFO][get-cart-content] Exported cart container HTML to ${mockPath}`) } // Get the HTML content after JavaScript execution html = await page.content() } finally { await browser.close() } } const $ = cheerio.load(html) return extractCartPageData($) } function extractCartPageData($: cheerio.CheerioAPI): CartContent { const $cartContainer = $('#sc-active-cart') // Check if cart is empty const emptyCartText = $cartContainer.text() if (emptyCartText.includes('Your Amazon Cart is empty')) { return { isEmpty: true, items: [], } } // Extract cart items const items: CartItem[] = [] $cartContainer.find('[data-asin]').each((_index, element) => { const $item = $(element) // Extract basic item information const titleElement = $item.find('a.sc-product-title').first() const title = titleElement.find('.a-truncate-full').text().trim() const price = $item.find('.apex-price-to-pay-value .a-offscreen').text().trim() const quantityElement = $item.find('[data-a-selector="value"]').text().trim() const quantity = parseInt(quantityElement) || 1 // Extract optional information const image = $item.find('.sc-product-image').attr('src') const productUrl = $item.find('.sc-product-link').attr('href') const asin = $item.attr('data-asin') const availability = $item.find('.sc-product-availability').text().trim() || 'Unknown' const isSelected = $item.find('input[type="checkbox"]').is(':checked') console.error(`[INFO][get-cart-content] Extracted ASIN: ${asin}, Price: ${price}, Quantity: ${quantity}, item: ${title}`) // Only add items with valid titles and prices if (title && price) { items.push({ title, price, quantity, image, productUrl, asin, availability, isSelected, }) } }) // Extract subtotal information const subtotal = $cartContainer.find('#sc-subtotal-amount-activecart .sc-price').text().trim() || $cartContainer.find('.sc-subtotal .sc-price').text().trim() const totalItemsText = $cartContainer.find('#sc-subtotal-label-activecart').text().trim() const totalItemsMatch = totalItemsText.match(/\((\d+)\s+item/) const totalItems = totalItemsMatch ? parseInt(totalItemsMatch[1]) : items.length return { isEmpty: false, items, subtotal, totalItems, } } // ################################## // Add to Cart // ################################## export async function addToCart(asin: string): Promise<{ success: boolean; message: string }> { if (!asin || asin.length !== 10) { throw new Error('Invalid ASIN provided. ASIN should be a 10-character string.') } const domain = getAmazonDomain() const url = `https://www.${domain}/-/en/gp/product/${asin}` console.error(`[INFO][add-to-cart] Adding product ${asin} to cart from ${url}`) const { browser, page } = await createBrowserAndPage() try { // Navigate to the product page await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }) // Handle login if needed await throwIfNotLoggedIn(page) // Wait for the page to load completely await page.waitForSelector('body', { timeout: 10000 }) try { // Check for subscribe and save option using XPath const xpath = "//div[contains(@class, 'accordion-caption')]//span[contains(text(), 'One-time purchase')]" const element = await page.waitForSelector(`::-p-xpath(${xpath})`, { timeout: 2000 }) if (element) { console.error(`[INFO][add-to-cart] The item is a subscribe and save product, clicking the one-time purchase option`) element.click() // Wait for the page to update await new Promise(resolve => setTimeout(resolve, 2000)) } else { console.error('[INFO][add-to-cart] No subscribe and save option found, proceeding to add to cart') } } catch (error) { console.error(`[INFO][add-to-cart] Error checking for subscribe and save option: ${error}`) } // Find and click the add to cart button try { await page.waitForSelector('#add-to-cart-button', { timeout: 10000 }) await page.click('#add-to-cart-button') console.error('[INFO][add-to-cart] Clicked add to cart button') } catch (error) { throw new Error(`Could not find or click the add to cart button: ${error}`) } // If there is an insurance option, refuse it try { await page.waitForSelector('#productTitle', { timeout: 1000 }) await page.click('#productTitle', { delay: 100 }) await page.click('#attachSiNoCoverage', { delay: 300 }) } catch (error) { console.error(`[WARNING][add-to-cart] Failed to click insurance option (it may not have been presented):`, error) } // Wait for the confirmation page/modal try { await page.waitForSelector('#sw-atc-confirmation', { timeout: 15000 }) // Check for success message const confirmationText = await page.$eval('#sw-atc-confirmation', el => el.textContent || '') if (!confirmationText.includes('Added to cart') && !confirmationText.includes('Added to basket')) { throw new Error(`Unexpected confirmation message: ${confirmationText}`) } console.error('[INFO][add-to-cart] Successfully added product to cart') return { success: true, message: `Product ${asin} successfully added to cart`, } } catch (error) { throw new Error(`Could not verify that the product was added to cart: ${error}`) } } finally { await browser.close() } } // ################################## // Clear Cart // ################################## export async function clearCart() { const domain = getAmazonDomain() const url = `https://www.${domain}/-/en/gp/cart/view.html` console.error(`[INFO][clear-cart] Clearing cart at ${url}`) const { browser, page } = await createBrowserAndPage() try { // Navigate to the cart page await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }) // Handle login if needed await throwIfNotLoggedIn(page) // Wait for the cart to load await page.waitForSelector('#sc-active-cart, .sc-cart-item, .sc-empty-cart-banner', { timeout: 10000 }) // Find all delete buttons const deleteButtons = await page.$$('span[data-action="delete-active"]') if (deleteButtons.length === 0) { console.error('[INFO][clear-cart] No items found in cart to remove') return { success: true, message: 'No items found in cart to remove', itemsRemoved: 0, } } console.error(`[INFO][clear-cart] Found ${deleteButtons.length} items to remove`) let itemsRemoved = 0 // Click each delete button with delay for (let i = 0; i < deleteButtons.length; i++) { try { // Re-query the delete buttons as DOM changes after each deletion const currentDeleteButtons = await page.$$('span[data-action="delete-active"]') if (currentDeleteButtons.length === 0) { console.error('[INFO][clear-cart] No more items to delete') break } // Click the first available delete button await currentDeleteButtons[0].click() itemsRemoved++ console.error(`[INFO][clear-cart] Removed item ${itemsRemoved}`) // Wait for the page to update after deletion if (i < deleteButtons.length - 1) { await new Promise(resolve => setTimeout(resolve, 800)) } } catch (error) { console.error(`[WARNING][clear-cart] Failed to remove item ${i + 1}:`, error) } } console.error(`[INFO][clear-cart] Successfully removed ${itemsRemoved} items from cart`) return { success: true, message: `Successfully cleared cart. Removed ${itemsRemoved} items.`, itemsRemoved, } } catch (error: any) { console.error('[ERROR][clear-cart] Error clearing cart:', error) throw new Error(`Failed to clear cart: ${error.message}`) } finally { await browser.close() } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/drahmad89/amazon-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server