mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 01:47:39 +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
|
||||
title: 'Bug: '
|
||||
title: 'bug: '
|
||||
labels: 'bug'
|
||||
assignees: jef
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature request
|
||||
name: 🚀 Feature request
|
||||
about: Suggest a feature for this project
|
||||
title: 'Enhancement: '
|
||||
title: 'enhancement: '
|
||||
labels: 'enhancement'
|
||||
assignees: jef
|
||||
|
||||
|
||||
@@ -16,23 +16,3 @@ jobs:
|
||||
package-name: nvidia-snatcher
|
||||
- name: login into github package registry
|
||||
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:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
name: Lint
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/setup-node@v2.1.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: 14
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-node-
|
||||
- name: Pull dependencies
|
||||
run: npm ci
|
||||
run: |
|
||||
npm ci
|
||||
npm run lint
|
||||
|
||||
+5
-1
@@ -1 +1,5 @@
|
||||
node_modules/
|
||||
.idea/
|
||||
build/
|
||||
node_modules/
|
||||
|
||||
.env
|
||||
|
||||
@@ -1,44 +1,86 @@
|
||||
# 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
|
||||
|
||||
## Installation and running
|
||||
|
||||
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.
|
||||
|
||||
Here's how to get it running:
|
||||
|
||||
- Save this text to a file in a folder on your Desktop. E.g. `nvidia/nvidia.js`
|
||||
- Open up your favorite terminal (`cmd`, `iTerm`, `Tilix`)
|
||||
- Run the below
|
||||
|
||||
```
|
||||
npm i puppeteer opn nodemailer
|
||||
node nvidia.js
|
||||
```sh
|
||||
2020-09-18T07:06:28.535Z info :: ✖ [nvidia] nvidia founders edition is still out of stock
|
||||
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
|
||||
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
|
||||
2020-09-18T07:06:43.318Z info :: ↗ trying stores again
|
||||
2020-09-18T07:06:43.318Z info :: 🚀🚀🚀 [nvidia] nvidia founders edition IN STOCK 🚀🚀🚀
|
||||
2020-09-18T07:06:43.318Z info :: https://store.nvidia.com/store/nvidia/en_US/buy/productID.5438481700/clearCart.yes/nextPage.QuickBuyCartPage
|
||||
```
|
||||
|
||||
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
|
||||
- Otherwise, use your regular 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 :)
|
||||
|
||||
```
|
||||
npm i puppeteer opn nodemailer
|
||||
EMAIL_USERNAME="youremail@gmail.com" EMAIL_PASSWORD="secretpassword" node nvidia.js
|
||||
```
|
||||
| | Best Buy | B&H | Newegg | Nvidia |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| 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.
|
||||
- `links` are specific to find the `"out of stock"` verbiage (forced to lowercase).
|
||||
## Installation and prerequisites
|
||||
|
||||
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
|
||||
|
||||
|
||||
Generated
+6187
File diff suppressed because it is too large
Load Diff
+17
-4
@@ -2,9 +2,12 @@
|
||||
"name": "nvidia-snatcher",
|
||||
"version": "1.0.0",
|
||||
"description": "🔮 For all your Nvidia needs",
|
||||
"main": "src/index.js",
|
||||
"main": "src/index.ts",
|
||||
"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": {
|
||||
"type": "git",
|
||||
@@ -18,8 +21,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/jef/nvidia-snatcher#readme",
|
||||
"dependencies": {
|
||||
"dotenv": "^8.2.0",
|
||||
"nodemailer": "^6.4.11",
|
||||
"opn": "^6.0.0",
|
||||
"puppeteer": "^5.3.0"
|
||||
"open": "^7.2.1",
|
||||
"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