mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 05:17:35 +00:00
feat: use ts, update cd, update README (#12)
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
EMAIL_USERNAME="youremail@gmail.com"
|
||||||
|
EMAIL_PASSWORD="secretpassword"
|
||||||
|
STORES="bestbuy,bandh,nvidia"
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
* @jef @andirew @davidlbowman @fuckingrobot @ioncaza @malbert69
|
* @jef
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: 🐛 Bug report
|
||||||
about: Report a bug for this project
|
about: Report a bug for this project
|
||||||
title: 'Bug: '
|
title: 'bug: '
|
||||||
labels: 'bug'
|
labels: 'bug'
|
||||||
assignees: jef
|
assignees: jef
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: 🚀 Feature request
|
||||||
about: Suggest a feature for this project
|
about: Suggest a feature for this project
|
||||||
title: 'Enhancement: '
|
title: 'enhancement: '
|
||||||
labels: 'enhancement'
|
labels: 'enhancement'
|
||||||
assignees: jef
|
assignees: jef
|
||||||
|
|
||||||
|
|||||||
@@ -16,23 +16,3 @@ jobs:
|
|||||||
package-name: nvidia-snatcher
|
package-name: nvidia-snatcher
|
||||||
- name: login into github package registry
|
- name: login into github package registry
|
||||||
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
|
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
|
||||||
- name: build nightly docker image
|
|
||||||
if: ${{ ! steps.release.outputs.release_created }}
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
-t "ghcr.io/${GITHUB_REPOSITORY}:${GITHUB_SHA:0:7}" \
|
|
||||||
-t "ghcr.io/${GITHUB_REPOSITORY}:nightly" .
|
|
||||||
- name: publish nightly
|
|
||||||
if: ${{ ! steps.release.outputs.release_created }}
|
|
||||||
run: docker push "ghcr.io/${GITHUB_REPOSITORY}"
|
|
||||||
- name: build latest docker image
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
-t "ghcr.io/${GITHUB_REPOSITORY}:${TAG_NAME}" \
|
|
||||||
-t "ghcr.io/${GITHUB_REPOSITORY}:latest" .
|
|
||||||
env:
|
|
||||||
TAG_NAME: ${{ steps.release.outputs.tag_name }}
|
|
||||||
- name: publish latest
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
run: docker push "ghcr.io/${GITHUB_REPOSITORY}"
|
|
||||||
|
|||||||
@@ -6,19 +6,17 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
name: Lint
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- uses: actions/setup-node@v2.1.1
|
- uses: actions/setup-node@v2.1.1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: 14
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: ${{ runner.os }}-node-
|
restore-keys: ${{ runner.os }}-node-
|
||||||
- name: Pull dependencies
|
- name: Pull dependencies
|
||||||
run: npm ci
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run lint
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
|
.idea/
|
||||||
|
build/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
.env
|
||||||
|
|||||||
@@ -1,44 +1,86 @@
|
|||||||
# nvidia-snatcher [](https://github.com/jef/nvidia-snatcher/actions?query=workflow%3Acd)
|
# nvidia-snatcher [](https://github.com/jef/nvidia-snatcher/actions?query=workflow%3Acd)
|
||||||
|
|
||||||
## Description
|
The purpose of this bot is to get an Nvidia card. It does multiple things to try to do that.
|
||||||
|
|
||||||
This is going to check Nvidia's website every 5 seconds to see if the 3080 is out of stock in the background. If it comes into stock, then your browser will open and direct you to a cart with the 3080 in it where you can proceed manually.
|
- Currently, `nvidia-snatcher` is not capable of purchasing a card for you
|
||||||
|
- Scrapes multiple websites for patterns of being stocked
|
||||||
|
- Opens browser when stock is available
|
||||||
|
- Send email to you when away from computer
|
||||||
|
- Must have Gmail
|
||||||
|
|
||||||
You may get false positives from time to time, so I apologize for that. If you're getting than more often than not, I would change the `const waitForTimeout = 1000;` to a higher number.
|
<details>
|
||||||
|
<summary>What you may see if you're lucky</summary>
|
||||||
|
|
||||||
**Update 1:** Now includes Best Buy 3080 FE
|
```sh
|
||||||
|
2020-09-18T07:06:28.535Z info :: ✖ [nvidia] nvidia founders edition is still out of stock
|
||||||
## Installation and running
|
2020-09-18T07:06:31.241Z info :: ✖ [nvidia] nvidia founders edition is still out of stock
|
||||||
|
2020-09-18T07:06:34.212Z info :: ✖ [bestbuy] nvidia founder edition is still out of stock
|
||||||
Not going to write a full write up here, but I'm going to assume you know Node.js. If you don't then go to Google and look up how to install for your OS.
|
2020-09-18T07:06:39.878Z info :: ✖ [bandh] gigabyte black is still out of stock
|
||||||
|
2020-09-18T07:06:43.236Z info :: ✖ [bestbuy] gigabyte black is still out of stock
|
||||||
Here's how to get it running:
|
2020-09-18T07:06:43.318Z info :: ↗ trying stores again
|
||||||
|
2020-09-18T07:06:43.318Z info :: 🚀🚀🚀 [nvidia] nvidia founders edition IN STOCK 🚀🚀🚀
|
||||||
- Save this text to a file in a folder on your Desktop. E.g. `nvidia/nvidia.js`
|
2020-09-18T07:06:43.318Z info :: https://store.nvidia.com/store/nvidia/en_US/buy/productID.5438481700/clearCart.yes/nextPage.QuickBuyCartPage
|
||||||
- Open up your favorite terminal (`cmd`, `iTerm`, `Tilix`)
|
|
||||||
- Run the below
|
|
||||||
|
|
||||||
```
|
|
||||||
npm i puppeteer opn nodemailer
|
|
||||||
node nvidia.js
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to get an email as well:
|
</details>
|
||||||
|
|
||||||
- If you have two-factor authentication, use https://myaccount.google.com/apppasswords to get your password Google app password
|
> :point_right: You may get false positives from time to time, so I apologize for that. The library currently waits for all calls to be completed before parsing, but sometimes this can unknown behavior. Patience is a virtue :)
|
||||||
- Otherwise, use your regular password
|
|
||||||
|
|
||||||
```
|
| | Best Buy | B&H | Newegg | Nvidia |
|
||||||
npm i puppeteer opn nodemailer
|
|:---:|:---:|:---:|:---:|:---:|
|
||||||
EMAIL_USERNAME="youremail@gmail.com" EMAIL_PASSWORD="secretpassword" node nvidia.js
|
| 3090 | | | | |
|
||||||
```
|
| 3080 | ✔ | ✔ | ℹ | ✔ |
|
||||||
|
| 3070 | | | | |
|
||||||
|
|
||||||
## Further customization and hacking
|
> :point_right: (ℹ) In the process of getting working. Catchpa problems are intermittent. Use if you'd like, but expect problems.
|
||||||
|
|
||||||
You can potentially add more `links` and change the `timeout` if you'd like.
|
[FAQ](#FAQ) | [Discord](https://discord.gg/3duFzwk) | [Issues](https://github.com/jef/nvidia-snatcher/issues)
|
||||||
|
|
||||||
- `timeout` and `waitForTimeout` are in milliseconds.
|
## Installation and prerequisites
|
||||||
- `links` are specific to find the `"out of stock"` verbiage (forced to lowercase).
|
|
||||||
|
Linux, macOS, and Windows are all capable operating systems.
|
||||||
|
|
||||||
|
You do not need any computer skills, smarts, or anything of that nature. You are very capable as you have made it this far. Some basic understanding how a terminal, git, and or Node.js is a bonus, but that does not limit you to getting `nvidia-snatcher` running!
|
||||||
|
|
||||||
|
- Download [Node.js 14](https://nodejs.org/en/)
|
||||||
|
- Download [git](https://git-scm.com/)
|
||||||
|
- Clone this project `https://github.com/jef/nvidia-snatcher.git`
|
||||||
|
- Run `npm install`
|
||||||
|
- Edit the `.env` file to your liking
|
||||||
|
- More on this in [customization](#Customization)
|
||||||
|
- Run `npm run start`
|
||||||
|
|
||||||
|
Then watch the magic happen!
|
||||||
|
|
||||||
|
### Customization
|
||||||
|
|
||||||
|
There is not much to configure (as of now), but there are some options that you can choose to utilize.
|
||||||
|
|
||||||
|
First, you're going to need to copy the `.env.example` to `.env`. The current options are:
|
||||||
|
|
||||||
|
| Environment variable | Description |
|
||||||
|
|:---:|:---:|
|
||||||
|
| `EMAIL_USERNAME` | Gmail address; e.g. `jensen.robbed.us@gmail.com` |
|
||||||
|
| `EMAIL_PASSWORD` | Gmail password; see below if you have MFA |
|
||||||
|
| `STORES` | List of stores you want to be scraped; optional, default: `nvidia` |
|
||||||
|
|
||||||
|
> :point_right: If you have multi-factor authentication (MFA), you will need to create an [app password](https://myaccount.google.com/apppasswords) and use this instead of your Gmail password.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Q: What's Node.js and how do I install it?** Visit [their website](https://nodejs.org/en/) and download and install it. Very straight forward. Otherwise, Google more information related to your system needs.
|
||||||
|
|
||||||
|
**Q: Will this harm my computer?** No.
|
||||||
|
|
||||||
|
**Q: Have you gotten a card yet?** No. :cry:
|
||||||
|
|
||||||
|
**Q: Will I get banned from of the stores?** Perhaps, but getting a card is a nice outcome.
|
||||||
|
|
||||||
|
**Q: I got a problem and need help!** File an [issue](https://github.com/jef/nvidia-snatcher/issues/new/choose), I'll do my best to get to you. I work a full time job and this is only a hobby of mine.
|
||||||
|
|
||||||
|
**Q: I'd love to contribute, how do I do that?** Make a [pull request](https://github.com/jef/nvidia-snatcher/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc)! All contributions are welcome.
|
||||||
|
|
||||||
|
**Q: Why do I have to download all this stuff just to get this bot working?** Well, I would rather you didn't either. See #11.
|
||||||
|
|
||||||
### Acknowledgements
|
### Acknowledgements
|
||||||
|
|
||||||
|
|||||||
Generated
+6187
File diff suppressed because it is too large
Load Diff
+17
-4
@@ -2,9 +2,12 @@
|
|||||||
"name": "nvidia-snatcher",
|
"name": "nvidia-snatcher",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "🔮 For all your Nvidia needs",
|
"description": "🔮 For all your Nvidia needs",
|
||||||
"main": "src/index.js",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js"
|
"build": "rimraf ./build && tsc",
|
||||||
|
"lint": "xo",
|
||||||
|
"lint:fix": "xo --fix",
|
||||||
|
"start": "npm run build && node build/index.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -18,8 +21,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/jef/nvidia-snatcher#readme",
|
"homepage": "https://github.com/jef/nvidia-snatcher#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
"nodemailer": "^6.4.11",
|
"nodemailer": "^6.4.11",
|
||||||
"opn": "^6.0.0",
|
"open": "^7.2.1",
|
||||||
"puppeteer": "^5.3.0"
|
"puppeteer": "^5.3.0",
|
||||||
|
"winston": "^3.3.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^14.11.1",
|
||||||
|
"@types/nodemailer": "^6.4.0",
|
||||||
|
"@types/puppeteer": "^3.0.2",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"typescript": "^4.0.2",
|
||||||
|
"xo": "^0.33.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import {resolve} from 'path';
|
||||||
|
import {config} from 'dotenv';
|
||||||
|
|
||||||
|
config({path: resolve(__dirname, '../.env')});
|
||||||
|
|
||||||
|
const email = {
|
||||||
|
username: process.env.EMAIL_USERNAME,
|
||||||
|
password: process.env.EMAIL_PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifications = {
|
||||||
|
email: email.username && email.password
|
||||||
|
};
|
||||||
|
|
||||||
|
const page = {
|
||||||
|
height: 1920,
|
||||||
|
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
|
||||||
|
width: 1080
|
||||||
|
};
|
||||||
|
|
||||||
|
const stores = process.env.STORES ?? 'nvidia';
|
||||||
|
|
||||||
|
export const Config = {
|
||||||
|
email,
|
||||||
|
notifications,
|
||||||
|
page,
|
||||||
|
rateLimitTimeout: 5000,
|
||||||
|
stores
|
||||||
|
};
|
||||||
-129
@@ -1,129 +0,0 @@
|
|||||||
const mailer = require("./mailer")
|
|
||||||
|
|
||||||
const puppeteer = require("puppeteer");
|
|
||||||
const opn = require("opn");
|
|
||||||
|
|
||||||
const timeout = 5000;
|
|
||||||
const waitForTimeout = 1000;
|
|
||||||
|
|
||||||
async function buy() {
|
|
||||||
const links = [
|
|
||||||
{
|
|
||||||
name: "nvidia.com",
|
|
||||||
url: "https://www.nvidia.com/en-us/geforce/buy/",
|
|
||||||
oosText: ["out of stock"],
|
|
||||||
cartUrl: "https://store.nvidia.com/store/nvidia/en_US/buy/productID.5438481700/clearCart.yes/nextPage.QuickBuyCartPage"
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "nvidia.com 2",
|
|
||||||
url: "https://www.nvidia.com/en-us/shop/geforce/?page=1&limit=9&locale=en-us&search=3080",
|
|
||||||
oosText: ["out of stock"],
|
|
||||||
cartUrl: "https://store.nvidia.com/store/nvidia/en_US/buy/productID.5438481700/clearCart.yes/nextPage.QuickBuyCartPage"
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "bestbuy.com",
|
|
||||||
url: "https://www.bestbuy.com/site/nvidia-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card-titanium-and-black/6429440.p?skuId=6429440",
|
|
||||||
oosText: ["sold out"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "newegg.com EVGA BLACK GAMING",
|
|
||||||
url: "https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522",
|
|
||||||
oosText: ["auto notify","out of stock"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "newegg.com MSI VENTUS",
|
|
||||||
url: "https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g/p/N82E16814137600",
|
|
||||||
oosText: ["auto notify","out of stock"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "bestbuy.com GIGABYTE BLACK",
|
|
||||||
url: "https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6430620.p?acampID=0&cmp=RMX&loc=Hatch&ref=198&skuId=6430620",
|
|
||||||
oosText: ["sold out"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "B&H GIGABYTE BLACK",
|
|
||||||
url: "https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html?SID=s1600391647213ytuua52439",
|
|
||||||
oosText: ["notify when available"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "newegg.com EVGA ARGB LED iCX3",
|
|
||||||
url: "https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3883-kr/p/N82E16814487521",
|
|
||||||
oosText: ["auto notify","out of stock"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "newegg.com EVGA XC3 ULTRA GAMING",
|
|
||||||
url: "https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3885-kr/p/N82E16814487520",
|
|
||||||
oosText: ["auto notify","out of stock"]
|
|
||||||
}
|
|
||||||
,{
|
|
||||||
name: "newegg.com ASUS TUF",
|
|
||||||
url: "https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453",
|
|
||||||
oosText: ["auto notify","out of stock"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
for (const link of links) {
|
|
||||||
await goto(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(buy, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function goto(link) {
|
|
||||||
const browser = await puppeteer.launch();
|
|
||||||
const page = await browser.newPage();
|
|
||||||
|
|
||||||
await page.setUserAgent(
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
|
||||||
);
|
|
||||||
|
|
||||||
page.setViewport({
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto(link.url);
|
|
||||||
await page.waitForTimeout(1000);
|
|
||||||
|
|
||||||
const dom = await page.evaluate(() => {
|
|
||||||
return {
|
|
||||||
body: document.body.innerText,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(dom);
|
|
||||||
|
|
||||||
if (isOutOfStock(dom.body, link.oosText)) {
|
|
||||||
console.log(link.name + " is still out of stock... Attempting next link.")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log("*** IN STOCK AT " + link.name.toUpperCase() + ", BUY NOW ***");
|
|
||||||
await page.screenshot({ path: `nvidia-${Date.now()}.png` });
|
|
||||||
let clickUrl;
|
|
||||||
if (link.cartUrl) {
|
|
||||||
clickUrl = link.cartUrl
|
|
||||||
} else {
|
|
||||||
clickUrl = link.url
|
|
||||||
}
|
|
||||||
opn(clickUrl);
|
|
||||||
mailer.send(clickUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOutOfStock(domText, oosTextArray) {
|
|
||||||
domText = domText.toLowerCase();
|
|
||||||
var result = false;
|
|
||||||
for(var text of oosTextArray) {
|
|
||||||
result = domText.includes(text.toLowerCase());
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
buy().then();
|
|
||||||
}
|
|
||||||
catch(error)
|
|
||||||
{
|
|
||||||
buy();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import {Config} from './config';
|
||||||
|
import {Store, Stores} from './store';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
import open from 'open';
|
||||||
|
import sendNotification from './notification';
|
||||||
|
import {Logger} from './logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the bot.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const results = [];
|
||||||
|
for (const store of Stores) {
|
||||||
|
Logger.debug(store.links);
|
||||||
|
results.push(lookup(store));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(results);
|
||||||
|
|
||||||
|
Logger.info('↗ trying stores again');
|
||||||
|
setTimeout(main, Config.rateLimitTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for looking up information about a each product within
|
||||||
|
* a `Store`. It's important that we ignore `no-await-in-loop` here
|
||||||
|
* because we don't want to get rate limited within the same store.
|
||||||
|
*
|
||||||
|
* @param store Vendor of graphics cards.
|
||||||
|
*/
|
||||||
|
async function lookup(store: Store) {
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
for (const link of store.links) {
|
||||||
|
const browser = await puppeteer.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await page.setUserAgent(Config.page.userAgent);
|
||||||
|
await page.setViewport({
|
||||||
|
height: Config.page.height,
|
||||||
|
width: Config.page.width
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(link.url, {waitUntil: 'networkidle0'});
|
||||||
|
|
||||||
|
const bodyHandle = await page.$('body');
|
||||||
|
const textContent = await page.evaluate(body => body.textContent, bodyHandle);
|
||||||
|
|
||||||
|
Logger.debug(textContent);
|
||||||
|
|
||||||
|
const graphicsCard = `${link.brand} ${link.model}`;
|
||||||
|
|
||||||
|
if (isOutOfStock(textContent, link.oosLabels)) {
|
||||||
|
Logger.info(`✖ [${store.name}] ${graphicsCard} is still out of stock`);
|
||||||
|
} else {
|
||||||
|
Logger.info(`🚀🚀🚀 [${store.name}] ${graphicsCard} IN STOCK 🚀🚀🚀`);
|
||||||
|
Logger.info(link.url);
|
||||||
|
|
||||||
|
Logger.debug('ℹ saving screenshot');
|
||||||
|
await page.screenshot({path: `success-${Date.now()}.png`});
|
||||||
|
|
||||||
|
const givenUrl = store.cartUrl ? store.cartUrl : link.url;
|
||||||
|
await open(givenUrl);
|
||||||
|
sendNotification(givenUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
/* eslint-enable no-await-in-loop */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if DOM has any out-of-stock related text.
|
||||||
|
*
|
||||||
|
* @param domText Complete DOM of website.
|
||||||
|
* @param oosLabels Out-of-stock labels.
|
||||||
|
*/
|
||||||
|
function isOutOfStock(domText: string, oosLabels: string[]) {
|
||||||
|
const domTextLowerCase = domText.toLowerCase();
|
||||||
|
let result = false;
|
||||||
|
for (const oosLabel of oosLabels) {
|
||||||
|
result = domTextLowerCase.includes(oosLabel.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will continually run until user interferes.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
void main();
|
||||||
|
} catch (error) {
|
||||||
|
// Ignoring errors; more than likely due to rate limits
|
||||||
|
Logger.error(error);
|
||||||
|
void main();
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import winston, {format} from 'winston';
|
||||||
|
|
||||||
|
const prettyJson = format.printf(info => {
|
||||||
|
if (typeof info.message === 'object') {
|
||||||
|
info.message = JSON.stringify(info.message, null, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${info.level} :: ${info.message}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Logger = winston.createLogger({
|
||||||
|
level: process.env.LOG_LEVEL ?? 'info',
|
||||||
|
format: format.combine(
|
||||||
|
format.colorize(),
|
||||||
|
format.prettyPrint(),
|
||||||
|
format.splat(),
|
||||||
|
format.simple(),
|
||||||
|
prettyJson
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({})
|
||||||
|
]
|
||||||
|
});
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
const nodemailer = require("nodemailer");
|
|
||||||
|
|
||||||
const emailUsername = process.env.EMAIL_USERNAME;
|
|
||||||
const emailPassword = process.env.EMAIL_PASSWORD;
|
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
service: "gmail",
|
|
||||||
auth: {
|
|
||||||
user: emailUsername,
|
|
||||||
pass: emailPassword,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let mailOptions = {
|
|
||||||
from: emailUsername,
|
|
||||||
to: emailUsername,
|
|
||||||
subject: "NVIDIA - BUY NOW",
|
|
||||||
text: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
function send(text) {
|
|
||||||
mailOptions.text = text;
|
|
||||||
if (emailUsername && emailPassword) {
|
|
||||||
transporter.sendMail(mailOptions, function (error, info) {
|
|
||||||
if (error) {
|
|
||||||
console.log(error);
|
|
||||||
} else {
|
|
||||||
console.log("email sent: " + info.response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { send }
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import Mail from 'nodemailer/lib/mailer';
|
||||||
|
import {Config} from '../config';
|
||||||
|
import {Logger} from '../logger';
|
||||||
|
|
||||||
|
const subject = 'NVIDIA - BUY NOW';
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: Config.email.username,
|
||||||
|
pass: Config.email.password
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mailOptions: Mail.Options = {
|
||||||
|
from: Config.email.username,
|
||||||
|
to: Config.email.username,
|
||||||
|
subject
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function sendEmail(text: string) {
|
||||||
|
mailOptions.text = text;
|
||||||
|
|
||||||
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
if (error) {
|
||||||
|
Logger.error(error);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
Logger.info(`✔ email sent: ${info.response}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {Config} from '../config';
|
||||||
|
import sendEmail from './email';
|
||||||
|
|
||||||
|
export default function sendNotification(cartUrl: string) {
|
||||||
|
if (Config.notifications.email) {
|
||||||
|
sendEmail(cartUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Store} from './store';
|
||||||
|
|
||||||
|
export const BAndH: Store = {
|
||||||
|
cartUrl: '',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
brand: 'gigabyte',
|
||||||
|
model: 'black',
|
||||||
|
url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html?SID=s1600391647213ytuua52439',
|
||||||
|
oosLabels: ['notify when available']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: 'bandh'
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {Store} from './store';
|
||||||
|
|
||||||
|
export const BestBuy: Store = {
|
||||||
|
cartUrl: '',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
brand: 'nvidia',
|
||||||
|
model: 'founder edition',
|
||||||
|
url: 'https://www.bestbuy.com/site/nvidia-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card-titanium-and-black/6429440.p?skuId=6429440',
|
||||||
|
oosLabels: ['sold out']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: 'gigabyte',
|
||||||
|
model: 'black',
|
||||||
|
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6430620.p?acampID=0&cmp=RMX&loc=Hatch&ref=198&skuId=6430620',
|
||||||
|
oosLabels: ['sold out']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: 'bestbuy'
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import {BestBuy} from './bestbuy';
|
||||||
|
import {BAndH} from './bandh';
|
||||||
|
import {NewEgg} from './newegg';
|
||||||
|
import {Nvidia} from './nvidia';
|
||||||
|
import {Config} from '../config';
|
||||||
|
|
||||||
|
const list = new Map([
|
||||||
|
['bestbuy', BestBuy],
|
||||||
|
['bandh', BAndH],
|
||||||
|
['newegg', NewEgg],
|
||||||
|
['nvidia', Nvidia]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!Config.stores.toLowerCase().includes('bestbuy')) {
|
||||||
|
list.delete('bestbuy');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.stores.toLowerCase().includes('bandh')) {
|
||||||
|
list.delete('bandh');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.stores.toLowerCase().includes('newegg')) {
|
||||||
|
list.delete('newegg');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.stores.toLowerCase().includes('nvidia')) {
|
||||||
|
list.delete('nvidia');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Stores = Array.from(list.values());
|
||||||
|
|
||||||
|
export * from './store';
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {Store} from './store';
|
||||||
|
|
||||||
|
export const NewEgg: Store = {
|
||||||
|
cartUrl: '',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
brand: 'asus',
|
||||||
|
model: 'tuf',
|
||||||
|
url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453',
|
||||||
|
oosLabels: ['auto notify', 'out of stock']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: 'evga',
|
||||||
|
model: 'black gaming',
|
||||||
|
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522',
|
||||||
|
oosLabels: ['auto notify', 'out of stock']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: 'evga',
|
||||||
|
model: 'argb led icx3',
|
||||||
|
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3883-kr/p/N82E16814487521',
|
||||||
|
oosLabels: ['auto notify', 'out of stock']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: 'evga',
|
||||||
|
model: 'xc3 ultra gaming',
|
||||||
|
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3885-kr/p/N82E16814487520',
|
||||||
|
oosLabels: ['auto notify', 'out of stock']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: 'msi',
|
||||||
|
model: 'ventus',
|
||||||
|
url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g/p/N82E16814137600',
|
||||||
|
oosLabels: ['auto notify', 'out of stock']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: 'newegg'
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {Store} from './store';
|
||||||
|
|
||||||
|
export const Nvidia: Store = {
|
||||||
|
cartUrl: 'https://store.nvidia.com/store/nvidia/en_US/buy/productID.5438481700/clearCart.yes/nextPage.QuickBuyCartPage',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
brand: 'nvidia',
|
||||||
|
model: 'founders edition',
|
||||||
|
url: 'https://www.nvidia.com/en-us/geforce/buy/',
|
||||||
|
oosLabels: ['out of stock']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: 'nvidia',
|
||||||
|
model: 'founders edition',
|
||||||
|
url: 'https://www.nvidia.com/en-us/shop/geforce/?page=1&limit=9&locale=en-us&search=3080',
|
||||||
|
oosLabels: ['out of stock']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: 'nvidia'
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
interface Link {
|
||||||
|
brand: string;
|
||||||
|
model: string;
|
||||||
|
url: string;
|
||||||
|
oosLabels: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
cartUrl: string;
|
||||||
|
links: Link[];
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": ["es6"],
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "build",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user