Skip to content
This repository was archived by the owner on Nov 20, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
3 changes: 2 additions & 1 deletion configTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
7 changes: 4 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Also: This is not intended to scalp massive quantities of PS5s. That shit aint cool.
6 changes: 4 additions & 2 deletions src/commands/scrape.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/contants.ts
Original file line number Diff line number Diff line change
@@ -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'
7 changes: 4 additions & 3 deletions src/extensions/scrape-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/utils/scrape-target-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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'
Expand Down
183 changes: 183 additions & 0 deletions src/utils/scrape-targetdigi-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import * as puppeteer from 'puppeteer'

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How different is this logic from the other target script? If it's mostly the URL, then I feel it's better to include another option in config.json to indicate digital,disc, or both and load the URL based on that. So,

  • we can add another prompt asking user to add device type (digital, disc, or both)
  • we can set scrape-target-util to accept a type argument, which tells us which URL to go to (digi or disc). If user set as both, then we can run scrape-target-util twice, one with the digi argument, the other with disc. This logic will probably be similar to other sites as well.

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();
}
}
1 change: 1 addition & 0 deletions src/utils/scrape-walmart-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down