diff --git a/bin/ps5bot b/bin/ps5bot.js similarity index 100% rename from bin/ps5bot rename to bin/ps5bot.js diff --git a/configTemplate.json b/configTemplate.json index b42e274..c2f95c6 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -10,8 +10,9 @@ "creditCardNumber": "", "expirationMonth": "MM format like 01 or 12", "expirationYear": "YYYY format like 2019", + "headless": "false", "cvv": "credit card CVV/security code. Usually 3-4 letters", "cronjobSchedule": "optional field. schedule to run the bot. In crontab format. ex: * 1 * * *", "targetEmail": "optional field. Only required to run on Target. ex: brickkace@gmail.com", "targetPassword": "optional field. Only required to run on Target. eganz245" -} \ No newline at end of file +} diff --git a/readme.md b/readme.md index df40d3d..53ca6a3 100644 --- a/readme.md +++ b/readme.md @@ -23,14 +23,15 @@ You do not need any computer skills, smarts, or anything of that nature. You are 7. Install dependencies by running `yarn` 8. Make CLI callable `yarn link` + 9. go to project directory `cd /the/project/directory/bin` ## Setup 1. Run ps5bot. You'll be prompted to fill in required checkout info - `ps5bot` + `node ps5bot` **Note: Below steps are still TODO** 2. Run scraper - `ps5bot scrape` + `node ps5bot scrape` - you will be asked to select the sites to run the bot. If you don't select anything, it will try to run on all websites. ## Bot Configs @@ -81,4 +82,4 @@ PS5bot exists to: - practice web scraping and to - buy a **single** PS5 for myself The second point is fair imo since it's pretty much an automated version of constantly clicking refresh to buy stuff. -Also: This is not intended to scalp massive quantities of PS5s. That shit aint cool. \ No newline at end of file +Also: This is not intended to scalp massive quantities of PS5s. That shit aint cool. diff --git a/src/commands/scrape.ts b/src/commands/scrape.ts index 4034ffc..c6ea07c 100644 --- a/src/commands/scrape.ts +++ b/src/commands/scrape.ts @@ -1,5 +1,5 @@ import { GluegunToolbox } from 'gluegun' -import { PLAYSTATION_DIRECT, TARGET, WALMART } from '../contants' +import { PLAYSTATION_DIRECT, TARGET, WALMART, TARGET_DIGITAL } from '../contants' module.exports = { name: 'scrape', @@ -23,7 +23,9 @@ module.exports = { scrape(PLAYSTATION_DIRECT) ]) } else { - if (sitesToScrape.includes(TARGET)) { + if (sitesToScrape.includes(TARGET_DIGITAL)) { + await scrape(TARGET_DIGITAL) + } else if (sitesToScrape.includes(TARGET)) { await scrape(TARGET) } else if (sitesToScrape.includes(WALMART)) { await scrape(WALMART) diff --git a/src/contants.ts b/src/contants.ts index 689bbe4..9cf2466 100644 --- a/src/contants.ts +++ b/src/contants.ts @@ -1,3 +1,4 @@ -export const TARGET = 'Target' +export const TARGET = 'Target Disc Edition' export const WALMART = 'Walmart' export const PLAYSTATION_DIRECT = 'PlayStation Direct' +export const TARGET_DIGITAL = 'Target Digital Edition' diff --git a/src/extensions/scrape-extension.ts b/src/extensions/scrape-extension.ts index 2b282aa..197682b 100644 --- a/src/extensions/scrape-extension.ts +++ b/src/extensions/scrape-extension.ts @@ -5,15 +5,16 @@ import { TARGET, WALMART } from '../contants' import { scrapeTarget } from '../utils/scrape-target-util' import { scrapeDirect } from '../utils/scrape-direct-util' import { scrapeWalmart } from '../utils/scrape-walmart-util' - +import { scrapeTargetDigi } from '../utils/scrape-targetdigi-util' module.exports = (toolbox: GluegunToolbox) => { toolbox.scrape = async (site: string) => { const config = JSON.parse(fs.readFileSync('./config.json', 'utf8')) const cronJobSchedule = config.cronSchedule let scraperToRun = scrapeDirect - - if (site === TARGET) { +if (site === TARGET_DIGITAL) { + scraperToRun = scrapeTargetDigi + } else if (site === TARGET) { scraperToRun = scrapeTarget } else if (site === WALMART) { scraperToRun = scrapeWalmart diff --git a/src/utils/scrape-target-util.ts b/src/utils/scrape-target-util.ts index 14eb693..8813662 100644 --- a/src/utils/scrape-target-util.ts +++ b/src/utils/scrape-target-util.ts @@ -33,6 +33,7 @@ export const scrapeTarget = async (config: { [key: string]: string }) => { try { const page = await browser.newPage() await page.setRequestInterception(true) + await page.setDefaultNavigationTimeout(0); page.on('request', async req => { if (req.resourceType() === 'image') { @@ -65,7 +66,7 @@ export const scrapeTarget = async (config: { [key: string]: string }) => { } await page.goto( - 'https://www.target.com/p/playstation-5-digital-edition-console/-/A-81114596' + 'https://www.target.com/p/playstation-5-console/-/A-81114595' ) // await page.goto( // 'https://www.target.com/p/dualsense-wireless-controller-for-playstation-5/-/A-81114477' diff --git a/src/utils/scrape-targetdigi-util.ts b/src/utils/scrape-targetdigi-util.ts new file mode 100644 index 0000000..ecfa899 --- /dev/null +++ b/src/utils/scrape-targetdigi-util.ts @@ -0,0 +1,183 @@ +import * as puppeteer from 'puppeteer' +import * as notifier from 'node-notifier' + +export const scrapeTargetDigi = async (config: { [key: string]: string }) => { + const { + phoneNumber, + firstName, + lastName, + state, + city, + zipCode, + address, + creditCardNumber, + expirationMonth, + expirationYear, + cvv, + targetEmail, + targetPassword + } = config + + if (!targetEmail || !targetPassword) { + throw new Error( + 'targetEmail and targetPassword settings not set in config.json' + ) + } + + const browser = await puppeteer.launch({ + headless: false, + args: ['--window-size=1920,1080'], + defaultViewport: null + }) + + try { + const page = await browser.newPage() + await page.setRequestInterception(true) + await page.setDefaultNavigationTimeout(0); + + page.on('request', async req => { + if (req.resourceType() === 'image') { + await req.abort() + } else { + await req.continue() + } + }) + + await page.goto('https://www.target.com') + const accountDropdown = await page.$('#account') + await accountDropdown.click() + + await page.waitForTimeout(6000) + const signInButton = await page.$('#accountNav-signIn') + await signInButton.click() + await page.waitForTimeout(6000) + await page.type('#username', targetEmail) + await page.type('#password', targetPassword) + await page.keyboard.press('Enter') + + await page.waitForTimeout(6000) + const isJoinRequest = await page.$('#circle-join-free') + if (isJoinRequest) { + console.log('join request exists') + const skipButton = await page.$('#circle-skip') + await skipButton.click() + } else { + console.log("join request doesn't exists") + } + + await page.goto( + 'https://www.target.com/p/playstation-5-digital-edition-console/-/A-81114596' + ) + // await page.goto( + // 'https://www.target.com/p/dualsense-wireless-controller-for-playstation-5/-/A-81114477' + // ) + + await page.waitForTimeout(4000) + + while (true) { + try { + await page.waitForSelector('button[data-test="shipItButton"]', { + timeout: 10000 + }) + break + } catch (error) { + await page.reload() + } + } + + const shipItButton = await page.$('button[data-test="shipItButton"]') + await shipItButton.click() + await page.waitForTimeout(4000) + + const noCoverageButton = await page.$( + 'button[data-test="espModalContent-declineCoverageButton"]' + ) + await noCoverageButton.click() + + await page.waitForTimeout(4000) + const addToCartModalViewCartCheckout = await page.$( + 'button[data-test="addToCartModalViewCartCheckout"]' + ) + await addToCartModalViewCartCheckout.click() + + await page.waitForTimeout(6000) + const checkoutButton = await page.$('button[data-test="checkout-button"]') + await checkoutButton.click() + + await page.waitForTimeout(6000) + const isCreditCardSavedAttemptOne = await page.$( + 'button[data-test="verify-card-button"]' + ) + if (isCreditCardSavedAttemptOne) { + await page.type('#creditCardInput-cardNumber', creditCardNumber) + // expiration date format: MM/YY e.g. 08/24 + await isCreditCardSavedAttemptOne.click() + await page.waitForTimeout(6000) + await page.type('#creditCardInput-cvv', cvv) + await page.keyboard.press('Enter') + } else { + // checkout page + const existingAddress = await page.$('div[data-test="address-0"]') + if (existingAddress) { + console.log('address exists') + await existingAddress.click() + const saveAndContinueButton = await page.$( + 'button[data-test="save-and-continue-button"]' + ) + await saveAndContinueButton.click() + } else { + console.log("address doesn't exists") + await page.type('#full_name', `${firstName} ${lastName}`) + await page.type('#address_line1', address) + await page.type('#zip_code', zipCode) + await page.type('#city', city) + await page.type('#mobile', phoneNumber) + await page.select('#state', state) + const saveAndContinueButton = await page.$( + 'button[data-test="saveButton"]' + ) + await saveAndContinueButton.click() + } + await page.waitForTimeout(6000) + await page.type('#creditCardInput-cardNumber', creditCardNumber) + + const isCreditCardSaved = await page.$( + 'button[data-test="verify-card-button"]' + ) + if (!isCreditCardSaved) { + // expiration date format: MM/YY e.g. 08/24 + await page.type( + '#creditCardInput-expiration', + `${expirationMonth}/${expirationYear.slice(2, 4)}` + ) + await page.type('#creditCardInput-cvv', cvv) + await page.type('#creditCardInput-cardName', `${firstName} ${lastName}`) + const saveAndContinueButton = await page.$( + 'button[data-test="save-and-continue-button"]' + ) + await saveAndContinueButton.click() + } else { + await isCreditCardSaved.click() + await page.waitForTimeout(6000) + await page.type('#creditCardInput-cvv', cvv) + await page.keyboard.press('Enter') + } + } + + notifier.notify({ + title: 'Target', + message: 'Ready to place order!', + sound: true + }) + + // await page.waitForTimeout(4000) + // const placeOrderButton = await page.$( + // 'button[data-test="placeOrderButton"]' + // ) + // await placeOrderButton.click() + } catch (error) { + console.log(error) + } finally { + // await browser.close(); + } +} diff --git a/src/utils/scrape-walmart-util.ts b/src/utils/scrape-walmart-util.ts index d072d3b..ef45235 100644 --- a/src/utils/scrape-walmart-util.ts +++ b/src/utils/scrape-walmart-util.ts @@ -26,6 +26,7 @@ export const scrapeWalmart = async (config: { [key: string]: string }) => { try { const page = await browser.newPage() await page.setRequestInterception(true) + await page.setDefaultNavigationTimeout(0); page.on('request', async req => { if (req.resourceType() === 'image') {