mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 02:57:34 +00:00
refactor: env, cartUrl optional, other consistencies
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
This commit is contained in:
@@ -28,13 +28,11 @@ The purpose of this bot is to get an Nvidia card. It tries multiple things to do
|
||||
|
||||
> :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 have unknown behavior. Patience is a virtue :)
|
||||
|
||||
| | **Best Buy** | **B&H** | **Newegg** | **Nvidia** | **EVGA** | **Amazon** |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **3070**| | | | | | |
|
||||
| **3080** | `✔` | `✔` | `ℹ` | `✔` | `✔` | `✔` |
|
||||
| **3090** | | | | | | |
|
||||
|
||||
> :point_right: (`ℹ`) Work in progress. Catchpa problems are intermittent. Use if you'd like, but expect problems.
|
||||
| | **Amazon** | **EVGA** | **Best Buy** | **B&H** | **Micro Center** | **Newegg** | **Nvidia** |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **3070**| | | | | | | |
|
||||
| **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` |
|
||||
| **3090** | | | | | | | |
|
||||
|
||||
## Installation and prerequisites
|
||||
|
||||
@@ -58,12 +56,12 @@ At any point you want the program to stop, use <kbd>Ctrl</kbd> + <kbd>C</kbd>.
|
||||
|
||||
### Customization
|
||||
|
||||
There is not much to configure (as of now), but there are some options that you can choose to utilize.
|
||||
To customize `nvidia-snatcher`, make a copy of `.env-example` as `.env` and make any changes to your liking.
|
||||
|
||||
First, you're going to need to copy the `.env.example` to `.env`. The current options are:
|
||||
Here is a list of variables that you can use to customize your newly copied `.env` file:
|
||||
|
||||
| **Environment variable** | **Description** |
|
||||
|:---:|:---:|
|
||||
|:---:|---|
|
||||
| `EMAIL_USERNAME` | Gmail address (e.g., `jensen.robbed.us@gmail.com`); optional |
|
||||
| `EMAIL_PASSWORD` | Gmail password; see below if you have MFA; optional |
|
||||
| `NOTIFICATION_TEST` | Test all the notifications configured; optional, default: `false` |
|
||||
@@ -71,6 +69,7 @@ First, you're going to need to copy the `.env.example` to `.env`. The current op
|
||||
| `PHONE_NUMBER` | 10 digit phone number (e.g., `1234567890`); optional, email configuration required |
|
||||
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS; optional, email configuration required |
|
||||
| `RATE_LIMIT_TIMEOUT` | Rate limit timeout for each full store cycle; optional, default: `5000` |
|
||||
| `SHOW_ONLY_BRANDS` | If set, will only show specified brands, seperated by `,` |
|
||||
| `SLACK_CHANNEL` | Slack channel for posting (e.g., `update`); optional |
|
||||
| `SLACK_TOKEN` | Slack API token; optional |
|
||||
| `STORES` | [Supported stores](#supported-stores) you want to be scraped; optional, default: `nvidia` |
|
||||
@@ -79,7 +78,6 @@ First, you're going to need to copy the `.env.example` to `.env`. The current op
|
||||
| `SCREENSHOT` | Capture screenshot of page on successful hit; optional, default `true` |
|
||||
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token; optional |
|
||||
| `TELEGRAM_CHAT_ID` | Telegram chat ID; optional |
|
||||
| `SHOW_ONLY_BRANDS` | If set, will only show specified brands, seperated by `,` |
|
||||
|
||||
> :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.
|
||||
|
||||
@@ -97,18 +95,17 @@ First, you're going to need to copy the `.env.example` to `.env`. The current op
|
||||
| Newegg | `newegg`|
|
||||
| Nvidia | `nvidia`|
|
||||
|
||||
> :point_right: Look at [`.env.example`](.env.example) for an example for `.env`.
|
||||
|
||||
#### Supported carriers
|
||||
|
||||
| **Carrier** | **Environment variable** |
|
||||
|:---:|:---:|
|
||||
| AT&T | `att`|
|
||||
| Google | `google`|
|
||||
| Sprint | `sprint`|
|
||||
| Telus | `telus`|
|
||||
| T-Mobile | `tmobile`|
|
||||
| Verizon | `verizone`|
|
||||
| **Carrier** | **Environment variable** | **Notes** |
|
||||
|:---:|:---:|:---:|
|
||||
| AT&T | `att`| |
|
||||
| Google | `google`| |
|
||||
| Mint | `mint`| |
|
||||
| Sprint | `sprint`| |
|
||||
| Telus | `telus`| |
|
||||
| T-Mobile | `tmobile`| |
|
||||
| Verizon | `verizon`| Works with Visible |
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
+9
-12
@@ -21,20 +21,20 @@ const notifications = {
|
||||
carrier: process.env.PHONE_CARRIER ?? '',
|
||||
number: process.env.PHONE_NUMBER ?? ''
|
||||
},
|
||||
slack: {
|
||||
channel: process.env.SLACK_CHANNEL ?? '',
|
||||
token: process.env.SLACK_TOKEN ?? ''
|
||||
},
|
||||
playSound: process.env.PLAY_SOUND ?? 'false',
|
||||
pushover: {
|
||||
token: process.env.PUSHOVER_TOKEN,
|
||||
user: process.env.PUSHOVER_USER
|
||||
},
|
||||
slack: {
|
||||
channel: process.env.SLACK_CHANNEL ?? '',
|
||||
token: process.env.SLACK_TOKEN ?? ''
|
||||
},
|
||||
telegram: {
|
||||
botToken: process.env.TELEGRAM_ACCESS_TOKEN ?? '',
|
||||
accessToken: process.env.TELEGRAM_ACCESS_TOKEN ?? '',
|
||||
chatId: process.env.TELEGRAM_CHAT_ID ?? ''
|
||||
},
|
||||
test: process.env.NOTIFICATION_TEST ?? 'false',
|
||||
playSound: process.env.PLAY_SOUND ?? 'false'
|
||||
test: process.env.NOTIFICATION_TEST ?? 'false'
|
||||
};
|
||||
|
||||
const page = {
|
||||
@@ -45,12 +45,9 @@ const page = {
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
|
||||
};
|
||||
|
||||
const rateLimitTimeout = Number(process.env.RATE_LIMIT_TIMEOUT) ?? 5000;
|
||||
|
||||
const stores = process.env.STORES ?? 'nvidia';
|
||||
|
||||
const openBrowser = process.env.OPEN_BROWSER ?? 'true';
|
||||
|
||||
const rateLimitTimeout = Number(process.env.RATE_LIMIT_TIMEOUT) ?? 5000;
|
||||
const stores = process.env.STORES ? process.env.STORES.split(',') : ['nvidia'];
|
||||
const showOnlyBrands = process.env.SHOW_ONLY_BRANDS ? process.env.SHOW_ONLY_BRANDS.split(',') : [];
|
||||
|
||||
export const Config = {
|
||||
|
||||
@@ -3,8 +3,8 @@ import Mail from 'nodemailer/lib/mailer';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
|
||||
const subject = 'NVIDIA - BUY NOW';
|
||||
const email = Config.notifications.email;
|
||||
const subject = 'NVIDIA - BUY NOW';
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
|
||||
@@ -3,34 +3,36 @@ import {sendEmail} from './email';
|
||||
import {sendSMS} from './sms';
|
||||
import {playSound} from './sound';
|
||||
import {sendSlackMessage} from './slack';
|
||||
import sendPushoverNotification from './pushover';
|
||||
import {sendPushoverNotification} from './pushover';
|
||||
import {sendTelegramMessage} from './telegram';
|
||||
|
||||
const notifications = Config.notifications;
|
||||
|
||||
export function sendNotification(cartUrl: string) {
|
||||
if (Config.notifications.email.username && Config.notifications.email.password) {
|
||||
if (notifications.email.username && notifications.email.password) {
|
||||
sendEmail(cartUrl);
|
||||
}
|
||||
|
||||
if (Config.notifications.slack.channel && Config.notifications.slack.token) {
|
||||
if (notifications.slack.channel && notifications.slack.token) {
|
||||
sendSlackMessage(cartUrl);
|
||||
}
|
||||
|
||||
if (Config.notifications.telegram.botToken && Config.notifications.telegram.chatId) {
|
||||
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
|
||||
sendTelegramMessage(cartUrl);
|
||||
}
|
||||
|
||||
if (Config.notifications.phone.number) {
|
||||
const carrier = Config.notifications.phone.carrier.toLowerCase();
|
||||
if (carrier && Config.notifications.phone.availableCarriers.has(carrier)) {
|
||||
if (notifications.phone.number) {
|
||||
const carrier = notifications.phone.carrier.toLowerCase();
|
||||
if (carrier && notifications.phone.availableCarriers.has(carrier)) {
|
||||
sendSMS(cartUrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.notifications.pushover.token && Config.notifications.pushover.user) {
|
||||
if (notifications.pushover.token && notifications.pushover.user) {
|
||||
sendPushoverNotification(cartUrl);
|
||||
}
|
||||
|
||||
if (Config.notifications.playSound !== 'false') {
|
||||
if (notifications.playSound === 'true') {
|
||||
playSound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import Push from 'pushover-notifications';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import Push = require('pushover-notifications');
|
||||
|
||||
const p = new Push({
|
||||
user: Config.notifications.pushover.user,
|
||||
token: Config.notifications.pushover.token
|
||||
const pushover = Config.notifications.pushover;
|
||||
const push = new Push({
|
||||
user: pushover.user,
|
||||
token: pushover.token
|
||||
});
|
||||
|
||||
export default function sendPushoverNotification(text: string) {
|
||||
export function sendPushoverNotification(text: string) {
|
||||
const message = {
|
||||
message: text
|
||||
};
|
||||
|
||||
p.send(message, (err: Error, result: string) => {
|
||||
push.send(message, (err: Error, result: string) => {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
} else {
|
||||
|
||||
@@ -2,18 +2,17 @@ import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import {TelegramClient} from 'messaging-api-telegram';
|
||||
|
||||
const chatId = Config.notifications.telegram.chatId;
|
||||
const accessToken = Config.notifications.telegram.botToken;
|
||||
const telegram = Config.notifications.telegram;
|
||||
|
||||
const client = new TelegramClient({
|
||||
accessToken
|
||||
accessToken: telegram.accessToken
|
||||
});
|
||||
|
||||
export function sendTelegramMessage(text: string) {
|
||||
(async () => {
|
||||
try {
|
||||
await client.sendMessage(chatId, text);
|
||||
Logger.info(`✔ telegram message sent to '${chatId}': ${text}`);
|
||||
await client.sendMessage(telegram.chatId, text);
|
||||
Logger.info(`✔ telegram message sent to '${telegram.chatId}': ${text}`);
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {Logger} from '../logger';
|
||||
import open from 'open';
|
||||
import {Store} from './model';
|
||||
import {sendNotification} from '../notification';
|
||||
import {isOutOfStock as includesLabels} from './out-of-stock';
|
||||
import {includesLabels} from './out-of-stock';
|
||||
|
||||
/**
|
||||
* Returns true if the brand should be checked for stock
|
||||
|
||||
@@ -1,74 +1,84 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const amazonca: Store = {
|
||||
cartUrl: '',
|
||||
export const AmazonCa: Store = {
|
||||
links: [
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming trio',
|
||||
url: 'https://www.amazon.ca/MSI-GeForce-RTX-3080-10G/dp/B08HR7SV3M?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3gaming',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Technology-Backplate-10G-P5-3895-KR/dp/B08HR3DPGW?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3ultra',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Technology-Backplate-10G-P5-3897-KR/dp/B08HR3Y5GQ?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3ultra',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Cooling-Backplate-10G-P5-3885-KR/dp/B08HR55YB5?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3gaming',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Cooling-Backplate-10G-P5-3883-KR/dp/B08HR4RJ3Q?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3black',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Gaming-Cooling-10G-P5-3881-KR/dp/B08HR6FMF3?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'windforce',
|
||||
url: 'https://www.amazon.ca/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080GAMING/dp/B08HJTH61J?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'windforce eagle',
|
||||
url: 'https://www.amazon.ca/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080EAGLE/dp/B08HJS2JLJ?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
url: 'https://www.amazon.ca/Asus-90YV0FB0-M0AM00-TUF-RTX3080-10G-GAMING/dp/B08HHDP9DW?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tufoc',
|
||||
url: 'https://www.amazon.ca/Asus-90YV0FB1-M0AM00-TUF-RTX3080-O10G-GAMING/dp/B08HH5WF97?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus',
|
||||
url: 'https://www.amazon.ca/MSI-GeForce-RTX-3080-10G/dp/B08HR5SXPS?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable', 'enter the characters you see below']
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
}
|
||||
],
|
||||
name: 'amazonca'
|
||||
name: 'amazonCa'
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Amazon: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'pny',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const BAndH: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const BestBuy: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'asus',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Evga: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'evga',
|
||||
|
||||
@@ -6,11 +6,11 @@ import {Amazon} from './amazon';
|
||||
import {MicroCenter} from './microcenter';
|
||||
import {Config} from '../../config';
|
||||
import {Nvidia} from './nvidia';
|
||||
import {amazonca} from './amazonca';
|
||||
import {AmazonCa} from './amazon-ca';
|
||||
|
||||
const masterList = new Map([
|
||||
['amazon', Amazon],
|
||||
['amazonca', amazonca],
|
||||
['amazonca', AmazonCa],
|
||||
['bestbuy', BestBuy],
|
||||
['bandh', BAndH],
|
||||
['evga', Evga],
|
||||
@@ -21,9 +21,7 @@ const masterList = new Map([
|
||||
|
||||
const list = new Map();
|
||||
|
||||
const storeArray = Config.stores.split(',');
|
||||
|
||||
for (const name of storeArray) {
|
||||
for (const name of Config.stores) {
|
||||
list.set(name, masterList.get(name));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const MicroCenter: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'evga',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const NewEgg: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'asus',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Nvidia: Store = {
|
||||
cartUrl: '',
|
||||
links: [
|
||||
{
|
||||
brand: 'nvidia',
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Link {
|
||||
}
|
||||
|
||||
export interface Store {
|
||||
cartUrl: string;
|
||||
cartUrl?: string;
|
||||
links: Link[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @param domText Complete DOM of website.
|
||||
* @param oosLabels Out-of-stock labels.
|
||||
*/
|
||||
export function isOutOfStock(domText: string, oosLabels: string[]) {
|
||||
export function includesLabels(domText: string, oosLabels: string[]): boolean {
|
||||
const domTextLowerCase = domText.toLowerCase();
|
||||
return oosLabels.some(label => domTextLowerCase.includes(label));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user