From 8c5d7d0c49c8f799f96f02293d88d67a5c7ba870 Mon Sep 17 00:00:00 2001 From: Jef LeCompte Date: Sat, 19 Sep 2020 17:35:46 -0400 Subject: [PATCH] refactor: env, `cartUrl` optional, other consistencies Signed-off-by: Jef LeCompte --- .env.example => .env-example | 0 README.md | 39 +++++++++---------- src/config.ts | 21 +++++----- src/notification/email.ts | 2 +- src/notification/notification.ts | 20 +++++----- src/notification/pushover.ts | 13 ++++--- src/notification/telegram.ts | 9 ++--- src/store/lookup.ts | 2 +- src/store/model/{amazonca.ts => amazon-ca.ts} | 38 +++++++++++------- src/store/model/amazon.ts | 1 - src/store/model/bandh.ts | 1 - src/store/model/bestbuy.ts | 1 - src/store/model/evga.ts | 1 - src/store/model/index.ts | 8 ++-- src/store/model/microcenter.ts | 1 - src/store/model/newegg.ts | 1 - src/store/model/nvidia.ts | 1 - src/store/model/store.ts | 2 +- src/store/out-of-stock.ts | 2 +- 19 files changed, 80 insertions(+), 83 deletions(-) rename .env.example => .env-example (100%) rename src/store/model/{amazonca.ts => amazon-ca.ts} (60%) diff --git a/.env.example b/.env-example similarity index 100% rename from .env.example rename to .env-example diff --git a/README.md b/README.md index e889b53..fb02167 100644 --- a/README.md +++ b/README.md @@ -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 Ctrl + C. ### 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 diff --git a/src/config.ts b/src/config.ts index d8fc260..315601f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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 = { diff --git a/src/notification/email.ts b/src/notification/email.ts index 6a104e7..ef108d3 100644 --- a/src/notification/email.ts +++ b/src/notification/email.ts @@ -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', diff --git a/src/notification/notification.ts b/src/notification/notification.ts index 0575509..28feaaf 100644 --- a/src/notification/notification.ts +++ b/src/notification/notification.ts @@ -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(); } } diff --git a/src/notification/pushover.ts b/src/notification/pushover.ts index f0d6138..50e9c0c 100644 --- a/src/notification/pushover.ts +++ b/src/notification/pushover.ts @@ -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 { diff --git a/src/notification/telegram.ts b/src/notification/telegram.ts index c56964b..6b8025a 100644 --- a/src/notification/telegram.ts +++ b/src/notification/telegram.ts @@ -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); } diff --git a/src/store/lookup.ts b/src/store/lookup.ts index 58e6b75..800308a 100644 --- a/src/store/lookup.ts +++ b/src/store/lookup.ts @@ -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 diff --git a/src/store/model/amazonca.ts b/src/store/model/amazon-ca.ts similarity index 60% rename from src/store/model/amazonca.ts rename to src/store/model/amazon-ca.ts index a2030aa..7b4eb01 100644 --- a/src/store/model/amazonca.ts +++ b/src/store/model/amazon-ca.ts @@ -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' }; diff --git a/src/store/model/amazon.ts b/src/store/model/amazon.ts index 3b9737e..30ccb13 100644 --- a/src/store/model/amazon.ts +++ b/src/store/model/amazon.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const Amazon: Store = { - cartUrl: '', links: [ { brand: 'pny', diff --git a/src/store/model/bandh.ts b/src/store/model/bandh.ts index 2c9bd18..63439dc 100644 --- a/src/store/model/bandh.ts +++ b/src/store/model/bandh.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const BAndH: Store = { - cartUrl: '', links: [ { brand: 'gigabyte', diff --git a/src/store/model/bestbuy.ts b/src/store/model/bestbuy.ts index 8496e82..f15ea21 100644 --- a/src/store/model/bestbuy.ts +++ b/src/store/model/bestbuy.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const BestBuy: Store = { - cartUrl: '', links: [ { brand: 'asus', diff --git a/src/store/model/evga.ts b/src/store/model/evga.ts index 229af67..bcc248d 100644 --- a/src/store/model/evga.ts +++ b/src/store/model/evga.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const Evga: Store = { - cartUrl: '', links: [ { brand: 'evga', diff --git a/src/store/model/index.ts b/src/store/model/index.ts index 3c2cc42..b36f697 100644 --- a/src/store/model/index.ts +++ b/src/store/model/index.ts @@ -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)); } diff --git a/src/store/model/microcenter.ts b/src/store/model/microcenter.ts index 2391baa..8a27583 100644 --- a/src/store/model/microcenter.ts +++ b/src/store/model/microcenter.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const MicroCenter: Store = { - cartUrl: '', links: [ { brand: 'evga', diff --git a/src/store/model/newegg.ts b/src/store/model/newegg.ts index 5c945b1..f7b2ae1 100644 --- a/src/store/model/newegg.ts +++ b/src/store/model/newegg.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const NewEgg: Store = { - cartUrl: '', links: [ { brand: 'asus', diff --git a/src/store/model/nvidia.ts b/src/store/model/nvidia.ts index 9376163..b11524b 100644 --- a/src/store/model/nvidia.ts +++ b/src/store/model/nvidia.ts @@ -1,7 +1,6 @@ import {Store} from './store'; export const Nvidia: Store = { - cartUrl: '', links: [ { brand: 'nvidia', diff --git a/src/store/model/store.ts b/src/store/model/store.ts index 7f5649f..ef7dbeb 100644 --- a/src/store/model/store.ts +++ b/src/store/model/store.ts @@ -7,7 +7,7 @@ interface Link { } export interface Store { - cartUrl: string; + cartUrl?: string; links: Link[]; name: string; } diff --git a/src/store/out-of-stock.ts b/src/store/out-of-stock.ts index 86d2010..7324883 100644 --- a/src/store/out-of-stock.ts +++ b/src/store/out-of-stock.ts @@ -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)); }