├── .gitignore ├── NeweggBot.js ├── README.md ├── config_template.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /config.json 3 | -------------------------------------------------------------------------------- /NeweggBot.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer') 2 | const config = require('./config.json') 3 | const log4js = require("log4js") 4 | 5 | const logger = log4js.getLogger("Newegg Shopping Bot") 6 | logger.level = "trace" 7 | 8 | /** 9 | * This value will set the ceiling on the random number of seconds to be added to the **refresh_time**. 10 | * To override the default value (11), set the randomized_wait_ceiling variable in the config.json. 11 | */ 12 | var randomizedWaitCeiling = config.randomized_wait_ceiling | 11; 13 | 14 | async function check_cart(page) { 15 | await page.waitForTimeout(250) 16 | const amountElementName = ".summary-content-total" 17 | try { 18 | await page.waitForSelector(amountElementName, { timeout: 1000 }) 19 | var amountElement = await page.$(amountElementName) 20 | var text = await page.evaluate(element => element.textContent, amountElement) 21 | // Check that the Cart total is not zero - indicating that the cart has items 22 | if (parseInt(text.split('$')[1]) === 0) { 23 | throw new Error("There are no items in the cart") 24 | } 25 | if (parseInt(text.split('$')[1]) > config.price_limit) { 26 | logger.error("Price exceeds limit, removing from cart") 27 | var button = await page.$$('button.btn.btn-mini') 28 | while (true) { 29 | try { 30 | await button[1].click() 31 | } catch (err) { 32 | break 33 | } 34 | } 35 | if (config.over_price_limit_behavior === "stop") { 36 | logger.error("Over Price Limit Behavior is 'stop'. Ending Newegg Shopping Bot process") 37 | process.exit(0); 38 | } else { 39 | return false 40 | } 41 | } 42 | logger.info("Item added to cart, attempting to purchase") 43 | return true 44 | } catch (err) { 45 | logger.error(err.message) 46 | var nextCheckInSeconds = config.refresh_time + Math.floor(Math.random() * Math.floor(randomizedWaitCeiling)) 47 | logger.info(`The next attempt will be performed in ${nextCheckInSeconds} seconds`) 48 | await page.waitForTimeout(nextCheckInSeconds * 1000) 49 | return false 50 | } 51 | } 52 | 53 | 54 | async function run() { 55 | logger.info("Newegg Shopping Bot Started") 56 | const browser = await puppeteer.launch({ 57 | headless: false, 58 | defaultViewport: { width: 1920, height: 1080 }, 59 | executablePath: config.browser_executable_path 60 | }) 61 | const page = await browser.newPage() 62 | await page.setCacheEnabled(false) 63 | while (true) { 64 | await page.goto('https://secure.newegg.com/NewMyAccount/AccountLogin.aspx', { waitUntil: 'networkidle0' }) 65 | if (page.url().includes('signin')) { 66 | await page.waitForSelector('button.btn.btn-orange') 67 | await page.type('#labeled-input-signEmail', config.email) 68 | await page.click('button.btn.btn-orange') 69 | await page.waitForTimeout(1500) 70 | try { 71 | await page.waitForSelector('#labeled-input-signEmail', { timeout: 500 }) 72 | } catch (err) { 73 | try { 74 | await page.waitForSelector('#labeled-input-password', { timeout: 2500 }) 75 | await page.waitForSelector('button.btn.btn-orange') 76 | await page.type('#labeled-input-password', config.password) 77 | await page.click('button.btn.btn-orange') 78 | await page.waitForTimeout(1500) 79 | try { 80 | await page.waitForSelector('#labeled-input-password', { timeout: 500 }) 81 | } catch (passwordSelectorErr) { 82 | break 83 | } 84 | } catch (passwordInputErr) { 85 | logger.warn("Manual authorization code required by Newegg. This should only happen once.") 86 | while (page.url().includes('signin')) { 87 | await page.waitForTimeout(500) 88 | } 89 | break 90 | } 91 | } 92 | } else if (page.url().includes("areyouahuman")) { 93 | await page.waitForTimeout(1000) 94 | } 95 | } 96 | 97 | logger.trace("Logged in") 98 | logger.info("Checking for Item") 99 | 100 | while (true) { 101 | try { 102 | await page.goto('https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=' + config.item_number, { waitUntil: 'networkidle0' }) 103 | if (page.url().includes("cart")) { 104 | if (await check_cart(page)) { 105 | break 106 | } 107 | } else if (page.url().includes("ShoppingItem")) { 108 | await page.goto('https://secure.newegg.com/Shopping/ShoppingCart.aspx', { waitUntil: 'load' }) 109 | if (await check_cart(page)) { 110 | break 111 | } 112 | } else if (page.url().includes("areyouahuman")) { 113 | await page.waitForTimeout(1000) 114 | } 115 | } catch (err) { 116 | continue 117 | } 118 | } 119 | 120 | // Find the "Secure Checkout" button and click it (if it exists) 121 | try { 122 | const [button] = await page.$x("//button[contains(., 'Secure Checkout')]") 123 | if (button) { 124 | logger.info("Starting Secure Checkout") 125 | await button.click() 126 | } 127 | } catch (err) { 128 | logger.error("Cannot find the Secure Checkout button") 129 | logger.error(err) 130 | } 131 | 132 | // Wait for the page 133 | await page.waitForTimeout(5000) 134 | try { 135 | await page.waitForSelector("#btnCreditCard", { timeout: 3000 }) 136 | await inputCVV(page) 137 | await submitOrder(page) 138 | } catch (err) { 139 | logger.error("Cannot find the Place Order button.") 140 | logger.warn("Please make sure that your Newegg account defaults for: shipping address, billing address, and payment method have been set.") 141 | } 142 | 143 | } 144 | 145 | /** 146 | * Input the Credit Verification Value (CVV) 147 | * @param {*} page The page containing the element 148 | */ 149 | async function inputCVV(page) { 150 | while (true) { 151 | logger.info("Waiting for CVV input element") 152 | try { 153 | await page.waitForSelector("[placeholder='CVV2']", { timeout: 3000 }) 154 | await page.focus("[placeholder='CVV2']", { timeout: 5000 }) 155 | await page.type("[placeholder='CVV2']", config.cv2) 156 | logger.info("CVV data inputted") 157 | break 158 | } catch (err) { 159 | logger.warn("Cannot find CVV input element") 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Submit the order 166 | * @param {*} page The page containing the order form 167 | */ 168 | async function submitOrder(page) { 169 | 170 | if (config.auto_submit) { 171 | await page.click('#btnCreditCard') 172 | logger.info("Completed purchase") 173 | } else { 174 | logger.warn("Order not submitted because 'auto_submit' is not enabled") 175 | } 176 | } 177 | 178 | 179 | run() 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeweggBot 2 | Autonomously buy products from Newegg as soon as they become available 3 | 4 | This bot is very much still in the early stages, and more than a little rough around the edges. Expect the occasional hiccups if you decide to use it. 5 | 6 | ## Installation 7 | You will require [Node.js 14](https://nodejs.org/en/) to run this. 8 | After installing via git or by downloading the code and extracting it, navigate to the folder where the files are located via powershell(or equivalent console) and run `npm install` command. If you end up experiencing the error `Error: Could not find browser revision latest` when running, you may also need to run the command `PUPPETEER-PRODUCT=firefox npm i puppeteer`. 9 | 10 | 11 | ## Configuration 12 | Once that is finished, create a copy of config_template.json and name it config.json. Inside you will find the very basic customization options. 13 | - `cv2` refers to the three digit code on the back of your credit card. 14 | - `refresh_time` refers to the duration to wait in seconds between add-to-cart attempts. This should be specified as a number, rather than a string. 15 | - `item_number` refers to Newegg's item number found at the end of the card page URL. For example, the item number for 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3897-kr/p/N82E16814487518' is N82E16814487518. This bot can attempt to buy multiple card models at once by including multiple item numbers separated by a comma. For example, 'N82E16814487518,N82E16814137598'. Be cautious with this however, as there are no checks in place to ensure that only one card is purchased, so if by chance two cards you're attempting to purchase come in stock at the same time, the bot would attempt to purchase both. 16 | - `auto_submit` refers to whether or not you want the bot to complete the checkout process. Setting it to 'true' will result in the bot completing the purchase, while 'false' will result in it completing all the steps up to but not including finalizing the purchase. It is mostly intended as a means to test that the bot is working without actually having it buy something. 17 | - `price_limit` refers to the maximum price that the bot will attempt to purchase a card for. It is based on the combined subtotal of your cart. 18 | - `over_price_limit_behavior` Defines the behavior for cases in which the cart total exceeds the specified `price_limit`. Currently, the only valid value is *"stop"*. This will instruct the bot to end the process when the cart is over the limit. The option was added as a string, as opposed to a boolean, to allow some flexibility for other potential actions. 19 | - `randomized_wait_ceiling` This value will set the ceiling on the random number of seconds to be added to the **refresh_time**. While not guaranteed, this should help to prevent - or at least delay - IP bans based on consistent traffic/timing. This should be specified as a number, rather than a string. 20 | - `browser_executable_path` This will set the path to the browser to be used by the bot. Depending on the browser selected, you *may* need to install additional packages. 21 | 22 | ## Usage 23 | After installation and configuration, the bot can then be run by using either `node neweggbot.js` or the `npm start` script. 24 | 25 | It is important if you've never used your Newegg account before that you setup your account with a valid address and payment information, and then run through the checkout process manually making any changes to shipping and payment as Newegg requests. You don't need to complete that purchase, just correct things so that when you click `Secure Checkout` from the cart, it brings you to `Review`, not `Shipping` or `Payment`. 26 | 27 | At the moment, in the event that a card comes in stock, but goes out of stock before the bot has been able to complete the purchase, it will likely break, and you will need to restart it. In general, there are very likely to be occasional issues that break the bot and require you to restart it. 28 | 29 | -------------------------------------------------------------------------------- /config_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "email":"email@email.com", 3 | "password":"supercoolpassword", 4 | "cv2":"123", 5 | "refresh_time": 5, 6 | "item_number":"N82E16814137595,N82E16814126455", 7 | "auto_submit":"true", 8 | "price_limit":"800", 9 | "over_price_limit_behavior": "stop", 10 | "randomized_wait_ceiling": 11, 11 | "browser_executable_path": "C:/Progra~2/Google/Chrome/Application/chrome.exe" 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neweggbot", 3 | "version": "1.0.0", 4 | "description": "Shopping Bot for NewEgg", 5 | "main": "NeweggBot.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node neweggbot.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Ataraksia/NeweggBot.git" 13 | }, 14 | "author": "Ataraksia", 15 | "contributors": ["Cololophier"], 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/Ataraksia/NeweggBot/issues" 19 | }, 20 | "homepage": "https://github.com/Ataraksia/NeweggBot#readme", 21 | "dependencies": { 22 | "log4js": "^6.3.0", 23 | "puppeteer": "^5.5.0" 24 | } 25 | } 26 | --------------------------------------------------------------------------------