diff --git a/src/store/lookup.ts b/src/store/lookup.ts index af7b0d8c34..9a7f45249b 100644 --- a/src/store/lookup.ts +++ b/src/store/lookup.ts @@ -91,7 +91,11 @@ async function lookup(browser: Browser, store: Store) { const givenUrl = link.cartUrl ? link.cartUrl : link.url; if (Config.browser.open) { - await open(givenUrl); + if (link.openCartAction === undefined) { + await open(givenUrl); + } else { + link.openCartAction(browser); + } } sendNotification(givenUrl, link); diff --git a/src/store/model/nvidia.ts b/src/store/model/nvidia.ts index dc9d8c5c84..82d568340f 100644 --- a/src/store/model/nvidia.ts +++ b/src/store/model/nvidia.ts @@ -1,20 +1,92 @@ import {Store} from './store'; +import {Browser, Response} from 'puppeteer'; +import {timestampUrlParameter} from '../timestamp-url-parameter'; +import {Logger} from '../../logger'; +import open from 'open'; + +const fe2060SuperId = 5379432500; +const fe3080Id = 5438481700; +const locale = 'en_us'; + +const nvidiaApiKey = '9485fa7b159e42edb08a83bde0d83dia'; + +function digitalRiverStockUrl(id: number): string { + return `https://api.digitalriver.com/v1/shoppers/me/products/${id}/inventory-status?` + + `&apiKey=${nvidiaApiKey}` + + timestampUrlParameter(); +} + +interface NvidiaSessionTokenJSON { + access_token: string; +} + +function nvidiaSessionUrl(): string { + return `https://store.nvidia.com/store/nvidia/SessionToken?format=json&locale=${locale}` + + `&apiKey=${nvidiaApiKey}` + + timestampUrlParameter(); +} + +function addToCartUrl(id: number, token: string): string { + return 'https://api.digitalriver.com/v1/shoppers/me/carts/active/line-items?format=json&method=post' + + `&productId=${id}` + + `&token=${token}` + + '&quantity=1' + + timestampUrlParameter(); +} + +function checkoutUrl(token: string): string { + return `https://api.digitalriver.com/v1/shoppers/me/carts/active/web-checkout?token=${token}`; +} + +function fallbackCartUrl(): string { + return `https://www.nvidia.com/en-us/shop/geforce?${timestampUrlParameter()}`; +} + +function generateCartAction(id: number, cardName: string) { + return async (browser: Browser) => { + const page = await browser.newPage(); + Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, starting auto add to cart... 🚀🚀🚀`); + let response: Response | null; + try { + Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, getting access token... 🚀🚀🚀`); + response = await page.goto(nvidiaSessionUrl(), {waitUntil: 'networkidle0'}); + if (response === null) { + throw new Error('NvidiaAccessTokenUnavailable'); + } + + const data = await response.json() as NvidiaSessionTokenJSON; + const accessToken = data.access_token; + + Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, adding to cart... 🚀🚀🚀`); + response = await page.goto(addToCartUrl(id, accessToken), {waitUntil: 'networkidle0'}); + + Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, opening checkout page... 🚀🚀🚀`); + Logger.info(checkoutUrl(accessToken)); + await open(checkoutUrl(accessToken)); + } catch { + Logger.error(`✖ [nvidia] ${cardName} could not automatically add to cart, opening page`); + await open(fallbackCartUrl()); + } + + await page.close(); + }; +} export const Nvidia: Store = { links: [ { + series: 'debug', brand: 'TEST', - cartUrl: 'https://www.nvidia.com/en-us/shop/geforce', model: 'CARD', - series: 'debug', - url: 'https://api.digitalriver.com/v1/shoppers/me/products/5379432500/inventory-status?apiKey=9485fa7b159e42edb08a83bde0d83dia' + url: digitalRiverStockUrl(fe2060SuperId), + openCartAction: generateCartAction(fe2060SuperId, 'TEST CARD') }, { + series: '3080', brand: 'nvidia', - cartUrl: 'https://www.nvidia.com/en-us/shop/geforce', model: 'founders edition', - series: '3080', - url: 'https://api.digitalriver.com/v1/shoppers/me/products/5438481700/inventory-status?apiKey=9485fa7b159e42edb08a83bde0d83dia' + url: digitalRiverStockUrl(fe3080Id), + openCartAction: generateCartAction(fe3080Id, 'nvidia founders edition 3080') } ], labels: { diff --git a/src/store/model/store.ts b/src/store/model/store.ts index 26dbd11ed8..400b9bbbb4 100644 --- a/src/store/model/store.ts +++ b/src/store/model/store.ts @@ -1,9 +1,12 @@ +import {Browser} from 'puppeteer'; + export interface Link { + series: string; brand: string; - cartUrl?: string; model: string; - series: string; url: string; + cartUrl?: string; + openCartAction?: (browser: Browser) => void; screenshot?: string; } diff --git a/src/store/timestamp-url-parameter.ts b/src/store/timestamp-url-parameter.ts new file mode 100644 index 0000000000..5a5950dc73 --- /dev/null +++ b/src/store/timestamp-url-parameter.ts @@ -0,0 +1,8 @@ +/** + * Generates unique URL param to prevent cached responses (similar to jQuery that Nvidia uses) + * + * @return string in format &=1111111111111 (time since epoch in ms) + */ +export function timestampUrlParameter(): string { + return `&_=${Date.now()}`; +}