chore: refactor config and fix audits (#406)

This commit is contained in:
Jef LeCompte
2020-10-03 13:15:39 -04:00
committed by GitHub
parent f006a8595b
commit 8098a31092
28 changed files with 1765 additions and 403 deletions
+1392 -30
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -23,6 +23,7 @@
},
"homepage": "https://github.com/jef/nvidia-snatcher#readme",
"dependencies": {
"@hijef/pushbullet": "^2.4.2",
"@slack/web-api": "^5.12.0",
"chalk": "^4.1.0",
"cheerio": "^1.0.0-rc.3",
@@ -38,7 +39,6 @@
"puppeteer-extra-plugin-adblocker": "^2.11.6",
"puppeteer-extra-plugin-block-resources": "^2.2.7",
"puppeteer-extra-plugin-stealth": "^2.6.2",
"pushbullet": "^2.4.0",
"pushover-notifications": "^1.2.2",
"twilio": "^3.49.4",
"twitter": "^1.7.1",
@@ -57,6 +57,7 @@
"rimraf": "^3.0.2",
"ts-node": "^9.0.0",
"typescript": "^4.0.2",
"webpack": "^4.44.2",
"xo": "^0.33.1"
},
"xo": {
+5 -5
View File
@@ -1,10 +1,10 @@
import {banner} from './banner';
console.log(banner);
import {config} from 'dotenv';
import {config as config_} from 'dotenv';
import path from 'path';
config({path: path.resolve(__dirname, '../.env')});
config_({path: path.resolve(__dirname, '../.env')});
/**
* Returns environment variable, given array, or default array.
@@ -91,7 +91,7 @@ const notifications = {
number: envOrString(process.env.PHONE_NUMBER)
},
playSound: envOrString(process.env.PLAY_SOUND),
pushBulletApiKey: envOrString(process.env.PUSHBULLET),
pushbullet: envOrString(process.env.PUSHBULLET),
pushover: {
priority: envOrString(process.env.PUSHOVER_PRIORITY),
token: envOrString(process.env.PUSHOVER_TOKEN),
@@ -128,8 +128,8 @@ const nvidia = {
const page = {
height: 1080,
inStockWaitTime: envOrNumber(process.env.IN_STOCK_WAIT_TIME),
navigationTimeout: envOrNumber(process.env.PAGE_TIMEOUT, 30000),
screenshot: envOrBoolean(process.env.SCREENSHOT),
timeout: envOrNumber(process.env.PAGE_TIMEOUT, 30000),
userAgent: envOrString(process.env.USER_AGENT, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'),
width: 1920
};
@@ -149,7 +149,7 @@ const store = {
stores: envOrArray(process.env.STORES, ['nvidia'])
};
export const Config = {
export const config = {
browser,
logLevel,
notifications,
+12 -12
View File
@@ -1,16 +1,16 @@
import {Config} from './config';
import {Logger} from './logger';
import {Stores} from './store/model';
import {adBlocker} from './adblocker';
import {config} from './config';
import {fetchLinks} from './store/fetch-links';
import {getSleepTime} from './util';
import {logger} from './logger';
import puppeteer from 'puppeteer-extra';
import resourceBlock from 'puppeteer-extra-plugin-block-resources';
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
import {tryLookupAndLoop} from './store';
puppeteer.use(stealthPlugin());
if (Config.browser.lowBandwidth) {
if (config.browser.lowBandwidth) {
puppeteer.use(resourceBlock({
blockedTypes: new Set(['image', 'font'])
}));
@@ -23,7 +23,7 @@ if (Config.browser.lowBandwidth) {
*/
async function main() {
if (Stores.length === 0) {
Logger.error('✖ no stores selected', Stores);
logger.error('✖ no stores selected', Stores);
return;
}
@@ -31,28 +31,28 @@ async function main() {
// Skip Chromium Linux Sandbox
// https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#setting-up-chrome-linux-sandbox
if (Config.browser.isTrusted) {
if (config.browser.isTrusted) {
args.push('--no-sandbox');
args.push('--disable-setuid-sandbox');
}
// Add the address of the proxy server if defined
if (Config.proxy.address) {
args.push(`--proxy-server=http://${Config.proxy.address}:${Config.proxy.port}`);
if (config.proxy.address) {
args.push(`--proxy-server=http://${config.proxy.address}:${config.proxy.port}`);
}
const browser = await puppeteer.launch({
args,
defaultViewport: {
height: Config.page.height,
width: Config.page.width
height: config.page.height,
width: config.page.width
},
headless: Config.browser.isHeadless
headless: config.browser.isHeadless
});
const promises = [];
for (const store of Stores) {
Logger.debug(store.links);
logger.debug(store.links);
if (store.setupAction !== undefined) {
store.setupAction(browser);
}
@@ -73,6 +73,6 @@ async function main() {
try {
void main();
} catch (error) {
Logger.error('✖ something bad happened, resetting nvidia-snatcher', error);
logger.error('✖ something bad happened, resetting nvidia-snatcher', error);
void main();
}
+5 -5
View File
@@ -1,7 +1,7 @@
import {Link, Store} from './store/model';
import winston, {format} from 'winston';
import {Config} from './config';
import chalk from 'chalk';
import {config} from './config';
const prettyJson = format.printf(info => {
const timestamp = new Date().toLocaleTimeString();
@@ -13,7 +13,7 @@ const prettyJson = format.printf(info => {
return chalk.grey(`[${timestamp}]`) + ` ${info.level} ` + chalk.grey('::') + ` ${info.message}`;
});
export const Logger = winston.createLogger({
export const logger = winston.createLogger({
format: format.combine(
format.colorize(),
format.prettyPrint(),
@@ -21,7 +21,7 @@ export const Logger = winston.createLogger({
format.simple(),
prettyJson
),
level: Config.logLevel,
level: config.logLevel,
transports: [new winston.transports.Console({})]
});
@@ -76,10 +76,10 @@ export const Print = {
},
maxPrice(link: Link, store: Store, price: number, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow(`PRICE ${price} EXCEEDS LIMIT ${Config.store.maxPrice}`);
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow(`PRICE ${price} EXCEEDS LIMIT ${config.store.maxPrice}`);
}
return `${buildProductString(link, store)} :: PRICE ${price} EXCEEDS LIMIT ${Config.store.maxPrice}`;
return `${buildProductString(link, store)} :: PRICE ${price} EXCEEDS LIMIT ${config.store.maxPrice}`;
},
message(message: string, topic: string, store: Store, color?: boolean): string {
if (color) {
+8 -2
View File
@@ -1,14 +1,20 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Print, logger} from '../logger';
import {config} from '../config';
import notifier from 'node-notifier';
const desktop = config.notifications.desktop;
export function sendDesktopNotification(link: Link, store: Store) {
if (desktop) {
logger.debug('↗ sending desktop notification');
(async () => {
notifier.notify({
message: link.cartUrl ? link.cartUrl : link.url,
title: Print.inStock(link, store)
});
Logger.info('✔ desktop notification sent');
logger.info('✔ desktop notification sent');
})();
}
}
+11 -6
View File
@@ -1,12 +1,16 @@
import {Link, Store} from '../store/model';
import {MessageBuilder, Webhook} from 'discord-webhook-node';
import {Config} from '../config';
import {Logger} from '../logger';
import {config} from '../config';
import {logger} from '../logger';
const hooks = Config.notifications.discord.webHookUrl;
const notifyGroup = Config.notifications.discord.notifyGroup;
const discord = config.notifications.discord;
const hooks = discord.webHookUrl;
const notifyGroup = discord.notifyGroup;
export function sendDiscordMessage(link: Link, store: Store) {
if (discord.webHookUrl.length > 0) {
logger.debug('↗ sending discord message');
(async () => {
try {
const embed = new MessageBuilder();
@@ -30,9 +34,10 @@ export function sendDiscordMessage(link: Link, store: Store) {
await Promise.all(promises);
Logger.info('✔ discord message sent');
logger.info('✔ discord message sent');
} catch (error) {
Logger.error('✖ couldn\'t send discord message', error);
logger.error('✖ couldn\'t send discord message', error);
}
})();
}
}
+9 -5
View File
@@ -1,10 +1,10 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Mail from 'nodemailer/lib/mailer';
import {config} from '../config';
import nodemailer from 'nodemailer';
const email = Config.notifications.email;
const email = config.notifications.email;
const transporter = nodemailer.createTransport({
auth: {
@@ -15,6 +15,9 @@ const transporter = nodemailer.createTransport({
});
export function sendEmail(link: Link, store: Store) {
if (email.username && email.password) {
logger.debug('↗ sending email');
const mailOptions: Mail.Options = {
attachments: link.screenshot ? [
{
@@ -30,9 +33,10 @@ export function sendEmail(link: Link, store: Store) {
transporter.sendMail(mailOptions, error => {
if (error) {
Logger.error('✖ couldn\'t send email', error);
logger.error('✖ couldn\'t send email', error);
} else {
Logger.info('✔ email sent');
logger.info('✔ email sent');
}
});
}
}
+2 -57
View File
@@ -1,11 +1,9 @@
import {Link, Store} from '../store/model';
import {Config} from '../config';
import {Logger} from '../logger';
import {playSound} from './sound';
import {sendDesktopNotification} from './desktop';
import {sendDiscordMessage} from './discord';
import {sendEmail} from './email';
import {sendPushBulletNotification} from './pushbullet';
import {sendPushbulletNotification} from './pushbullet';
import {sendPushoverNotification} from './pushover';
import {sendSMS} from './sms';
import {sendSlackMessage} from './slack';
@@ -13,69 +11,16 @@ import {sendTelegramMessage} from './telegram';
import {sendTweet} from './twitter';
import {sendTwilioMessage} from './twilio';
const notifications = Config.notifications;
export function sendNotification(link: Link, store: Store) {
if (notifications.email.username && notifications.email.password) {
Logger.debug('↗ sending email');
sendEmail(link, store);
}
if (notifications.phone.number) {
Logger.debug('↗ sending sms');
const carrier = notifications.phone.carrier;
if (carrier && notifications.phone.availableCarriers.has(carrier)) {
sendSMS(link, store);
}
}
if (notifications.playSound) {
Logger.debug('↗ playing sound');
playSound();
}
if (notifications.desktop) {
Logger.debug('↗ sending desktop notification');
sendDesktopNotification(link, store);
}
if (notifications.discord.webHookUrl.length > 0) {
Logger.debug('↗ sending discord message');
sendDiscordMessage(link, store);
}
if (notifications.slack.channel && notifications.slack.token) {
Logger.debug('↗ sending slack message');
sendSlackMessage(link, store);
}
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
Logger.debug('↗ sending telegram message');
sendTelegramMessage(link, store);
}
if (notifications.twilio.accountSid && notifications.twilio.authToken) {
Logger.debug('↗ sending twilio message');
sendTwilioMessage(link, store);
}
if (notifications.pushBulletApiKey) {
Logger.debug('↗ sending pushbullet message');
sendPushBulletNotification(link, store);
}
if (notifications.pushover.token && notifications.pushover.username) {
Logger.debug('↗ sending pushover message');
sendPushbulletNotification(link, store);
sendPushoverNotification(link, store);
}
if (
notifications.twitter.accessTokenKey &&
notifications.twitter.accessTokenSecret &&
notifications.twitter.consumerKey &&
notifications.twitter.consumerSecret
) {
Logger.debug('↗ sending twitter message');
sendTweet(link, store);
}
}
+12 -8
View File
@@ -1,12 +1,15 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import PushBullet from 'pushbullet';
import {Print, logger} from '../logger';
import PushBullet from '@hijef/pushbullet';
import {config} from '../config';
const pushBulletApiKey = Config.notifications.pushBulletApiKey;
const pushbullet = config.notifications.pushbullet;
export function sendPushBulletNotification(link: Link, store: Store) {
const pusher = new PushBullet(pushBulletApiKey);
export function sendPushbulletNotification(link: Link, store: Store) {
if (pushbullet) {
logger.debug('↗ sending pushbullet message');
const pusher = new PushBullet(pushbullet);
pusher.note(
{},
@@ -14,9 +17,10 @@ export function sendPushBulletNotification(link: Link, store: Store) {
link.cartUrl ? link.cartUrl : link.url,
(error: Error) => {
if (error) {
Logger.error('✖ couldn\'t send pushbullet message', error);
logger.error('✖ couldn\'t send pushbullet message', error);
} else {
Logger.info('✔ pushbullet message sent');
logger.info('✔ pushbullet message sent');
}
});
}
}
+9 -5
View File
@@ -1,15 +1,18 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Push from 'pushover-notifications';
import {config} from '../config';
const pushover = Config.notifications.pushover;
const pushover = config.notifications.pushover;
const push = new Push({
token: pushover.token,
user: pushover.username
});
export function sendPushoverNotification(link: Link, store: Store) {
if (pushover.token && pushover.username) {
logger.debug('↗ sending pushover message');
const message = {
message: link.cartUrl ? link.cartUrl : link.url,
priority: pushover.priority,
@@ -18,9 +21,10 @@ export function sendPushoverNotification(link: Link, store: Store) {
push.send(message, (error: Error) => {
if (error) {
Logger.error('✖ couldn\'t send pushover message', error);
logger.error('✖ couldn\'t send pushover message', error);
} else {
Logger.info('✔ pushover message sent');
logger.info('✔ pushover message sent');
}
});
}
}
+12 -7
View File
@@ -1,13 +1,17 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import {WebClient} from '@slack/web-api';
import {config} from '../config';
const channel = Config.notifications.slack.channel;
const token = Config.notifications.slack.token;
const slack = config.notifications.slack;
const channel = slack.channel;
const token = slack.token;
const web = new WebClient(token);
export function sendSlackMessage(link: Link, store: Store) {
if (slack.channel && slack.token) {
logger.debug('↗ sending slack message');
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
@@ -18,13 +22,14 @@ export function sendSlackMessage(link: Link, store: Store) {
});
if (!result.ok) {
Logger.error('✖ couldn\'t send slack message', result);
logger.error('✖ couldn\'t send slack message', result);
return;
}
Logger.info('✔ slack message sent');
logger.info('✔ slack message sent');
} catch (error) {
Logger.error('✖ couldn\'t send slack message', error);
logger.error('✖ couldn\'t send slack message', error);
}
})();
}
}
+15 -8
View File
@@ -1,14 +1,14 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Mail from 'nodemailer/lib/mailer';
import {config} from '../config';
import nodemailer from 'nodemailer';
if (Config.notifications.phone.number && !Config.notifications.email.username) {
Logger.warn('✖ in order to recieve sms alerts, email notifications must also be configured');
if (config.notifications.phone.number && !config.notifications.email.username) {
logger.warn('✖ in order to recieve sms alerts, email notifications must also be configured');
}
const [email, phone] = [Config.notifications.email, Config.notifications.phone];
const [email, phone] = [config.notifications.email, config.notifications.phone];
const transporter = nodemailer.createTransport({
auth: {
@@ -19,6 +19,11 @@ const transporter = nodemailer.createTransport({
});
export function sendSMS(link: Link, store: Store) {
if (phone.number) {
logger.debug('↗ sending sms');
const carrier = phone.carrier;
if (carrier && phone.availableCarriers.has(carrier)) {
const mailOptions: Mail.Options = {
attachments: link.screenshot ? [
{
@@ -34,12 +39,14 @@ export function sendSMS(link: Link, store: Store) {
transporter.sendMail(mailOptions, error => {
if (error) {
Logger.error('✖ couldn\'t send sms', error);
logger.error('✖ couldn\'t send sms', error);
} else {
Logger.info('✔ sms sent');
logger.info('✔ sms sent');
}
});
}
}
}
function generateAddress() {
const carrier = phone.carrier;
@@ -48,5 +55,5 @@ function generateAddress() {
return [phone.number, phone.availableCarriers.get(carrier)].join('@');
}
Logger.error('✖ unknown carrier', carrier);
logger.error('✖ unknown carrier', carrier);
}
+13 -11
View File
@@ -1,35 +1,37 @@
import {Config} from '../config';
import {Logger} from '../logger';
import {config} from '../config';
import fs from 'fs';
import {logger} from '../logger';
import playerLib from 'play-sound';
let player: any;
if (Config.notifications.playSound) {
if (config.notifications.playSound) {
player = playerLib();
if (player.player === null) {
Logger.warn('✖ couldn\'t find sound player');
logger.warn('✖ couldn\'t find sound player');
} else {
const playerName: string = player.player;
Logger.info(`✔ sound player found: ${playerName}`);
logger.info(`✔ sound player found: ${playerName}`);
}
}
export function playSound() {
if (player.player !== null) {
fs.access(Config.notifications.playSound, fs.constants.F_OK, error => {
if (config.notifications.playSound && player.player !== null) {
logger.debug('↗ playing sound');
fs.access(config.notifications.playSound, fs.constants.F_OK, error => {
if (error) {
Logger.error(`✖ error opening sound file: ${error.message}`);
logger.error(`✖ error opening sound file: ${error.message}`);
return;
}
player.play(Config.notifications.playSound, (error: Error) => {
player.play(config.notifications.playSound, (error: Error) => {
if (error) {
Logger.error('✖ couldn\'t play sound', error);
logger.error('✖ couldn\'t play sound', error);
}
Logger.info('✔ played sound');
logger.info('✔ played sound');
});
});
}
+9 -5
View File
@@ -1,23 +1,27 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import {TelegramClient} from 'messaging-api-telegram';
import {config} from '../config';
const telegram = Config.notifications.telegram;
const telegram = config.notifications.telegram;
const client = new TelegramClient({
accessToken: telegram.accessToken
});
export function sendTelegramMessage(link: Link, store: Store) {
if (telegram.accessToken && telegram.chatId) {
logger.debug('↗ sending telegram message');
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
try {
await client.sendMessage(telegram.chatId, `${Print.inStock(link, store)}\n${givenUrl}`);
Logger.info('✔ telegram message sent');
logger.info('✔ telegram message sent');
} catch (error) {
Logger.error('✖ couldn\'t send telegram message', error);
logger.error('✖ couldn\'t send telegram message', error);
}
})();
}
}
+17 -9
View File
@@ -1,25 +1,33 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import twilio from 'twilio';
import {Print, logger} from '../logger';
import {Twilio} from 'twilio';
import {config} from '../config';
const config = Config.notifications.twilio;
const twilio = config.notifications.twilio;
let client: Twilio;
if (twilio.accountSid && twilio.authToken) {
client = new Twilio(twilio.accountSid, twilio.authToken);
}
export function sendTwilioMessage(link: Link, store: Store) {
if (client) {
logger.debug('↗ sending twilio message');
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
const message = `${Print.inStock(link, store)}\n${givenUrl}`;
try {
const client = twilio(config.accountSid, config.authToken);
await client.messages.create({
body: message,
from: config.from,
to: config.to
from: twilio.from,
to: twilio.to
});
Logger.info('✔ twilio message sent');
logger.info('✔ twilio message sent');
} catch (error) {
Logger.error('✖ couldn\'t send twilio message', error);
logger.error('✖ couldn\'t send twilio message', error);
}
})();
}
}
+9 -5
View File
@@ -1,9 +1,9 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Twitter from 'twitter';
import {config} from '../config';
const twitter = Config.notifications.twitter;
const twitter = config.notifications.twitter;
const client = new Twitter({
access_token_key: twitter.accessTokenKey,
@@ -13,6 +13,9 @@ const client = new Twitter({
});
export function sendTweet(link: Link, store: Store) {
if (twitter.accessTokenKey && twitter.accessTokenSecret && twitter.consumerKey && twitter.consumerSecret) {
logger.debug('↗ sending twitter message');
let status = `${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`;
if (twitter.tweetTags) {
@@ -21,9 +24,10 @@ export function sendTweet(link: Link, store: Store) {
client.post('statuses/update', {status}, error => {
if (error) {
Logger.error('✖ couldn\'t send twitter notification', error);
logger.error('✖ couldn\'t send twitter notification', error);
} else {
Logger.info('✔ twitter notification sent');
logger.info('✔ twitter notification sent');
}
});
}
}
+6 -6
View File
@@ -1,5 +1,5 @@
import {Link, Series, Store} from './model';
import {Logger, Print} from '../logger';
import {Print, logger} from '../logger';
import {Browser} from 'puppeteer';
import cheerio from 'cheerio';
import {filterSeries} from './filter';
@@ -7,7 +7,7 @@ import {usingResponse} from '../util';
function addNewLinks(store: Store, links: Link[], series: Series) {
if (links.length === 0) {
Logger.error(Print.message('NO STORE LINKS FOUND', series, store, true));
logger.error(Print.message('NO STORE LINKS FOUND', series, store, true));
return;
}
@@ -19,8 +19,8 @@ function addNewLinks(store: Store, links: Link[], series: Series) {
return;
}
Logger.info(Print.message(`FOUND ${newLinks.length} STORE LINKS`, series, store, true));
Logger.debug(JSON.stringify(newLinks, null, 2));
logger.info(Print.message(`FOUND ${newLinks.length} STORE LINKS`, series, store, true));
logger.debug(JSON.stringify(newLinks, null, 2));
store.links = store.links.concat(newLinks);
}
@@ -37,13 +37,13 @@ export async function fetchLinks(store: Store, browser: Browser) {
continue;
}
Logger.info(Print.message('DETECTING STORE LINKS', series, store, true));
logger.info(Print.message('DETECTING STORE LINKS', series, store, true));
promises.push(usingResponse(browser, url, async response => {
const text = await response?.text();
if (!text) {
Logger.error(Print.message('NO RESPONSE', series, store, true));
logger.error(Print.message('NO RESPONSE', series, store, true));
return;
}
+7 -7
View File
@@ -1,5 +1,5 @@
import {Config} from '../config';
import {Link} from './model';
import {config} from '../config';
/**
* Returns true if the brand should be checked for stock
@@ -7,11 +7,11 @@ import {Link} from './model';
* @param brand The brand of the GPU
*/
function filterBrand(brand: Link['brand']): boolean {
if (Config.store.showOnlyBrands.length === 0) {
if (config.store.showOnlyBrands.length === 0) {
return true;
}
return Config.store.showOnlyBrands.includes(brand);
return config.store.showOnlyBrands.includes(brand);
}
/**
@@ -20,12 +20,12 @@ function filterBrand(brand: Link['brand']): boolean {
* @param model The model of the GPU
*/
function filterModel(model: Link['model']): boolean {
if (Config.store.showOnlyModels.length === 0) {
if (config.store.showOnlyModels.length === 0) {
return true;
}
const sanitizedModel = model.replace(/\s/g, '');
for (const configModel of Config.store.showOnlyModels) {
for (const configModel of config.store.showOnlyModels) {
const sanitizedConfigModel = configModel.replace(/\s/g, '');
if (sanitizedModel === sanitizedConfigModel) {
return true;
@@ -41,11 +41,11 @@ function filterModel(model: Link['model']): boolean {
* @param series The series of the GPU
*/
export function filterSeries(series: Link['series']): boolean {
if (Config.store.showOnlySeries.length === 0) {
if (config.store.showOnlySeries.length === 0) {
return true;
}
return Config.store.showOnlySeries.includes(series);
return config.store.showOnlySeries.includes(series);
}
/**
+3 -3
View File
@@ -1,6 +1,6 @@
import {Element, LabelQuery, Pricing} from './model';
import {Logger} from '../logger';
import {Page} from 'puppeteer';
import {logger} from '../logger';
export type Selector = {
requireVisible: boolean;
@@ -44,7 +44,7 @@ export async function pageIncludesLabels(page: Page, query: LabelQuery, options:
return false;
}
Logger.debug(contents);
logger.debug(contents);
return includesLabels(contents, query.text);
}));
@@ -103,7 +103,7 @@ export async function cardPriceLimit(page: Page, query: Pricing, max: number, op
const priceSeperator = query.euroFormat ? /\./g : /,/g;
const cardpriceNumber = Number.parseFloat(cardPrice.replace(priceSeperator, '').match(/\d+/g)!.join('.'));
Logger.debug(`Raw card price: ${cardPrice} | Limit: ${max}`);
logger.debug(`Raw card price: ${cardPrice} | Limit: ${max}`);
return cardpriceNumber > max ? cardpriceNumber : null;
}
+26 -26
View File
@@ -1,9 +1,9 @@
import {Browser, Page, Response} from 'puppeteer';
import {Link, Store} from './model';
import {Logger, Print} from '../logger';
import {Print, logger} from '../logger';
import {Selector, cardPriceLimit, pageIncludesLabels} from './includes-labels';
import {closePage, delay, getSleepTime, isStatusCodeInRange} from '../util';
import {Config} from '../config';
import {config} from '../config';
import {disableBlockerInPage} from '../adblocker';
import {filterStoreLink} from './filter';
import open from 'open';
@@ -27,20 +27,20 @@ async function lookup(browser: Browser, store: Store) {
continue;
}
if (Config.page.inStockWaitTime && inStock[link.url]) {
Logger.info(Print.inStockWaiting(link, store, true));
if (config.page.inStockWaitTime && inStock[link.url]) {
logger.info(Print.inStockWaiting(link, store, true));
continue;
}
const page = await browser.newPage();
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
await page.setUserAgent(Config.page.userAgent);
page.setDefaultNavigationTimeout(config.page.timeout);
await page.setUserAgent(config.page.userAgent);
if (store.disableAdBlocker) {
try {
await disableBlockerInPage(page);
} catch (error) {
Logger.error(error);
logger.error(error);
}
}
@@ -49,7 +49,7 @@ async function lookup(browser: Browser, store: Store) {
try {
statusCode = await lookupCard(browser, store, page, link);
} catch (error) {
Logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`);
logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`);
}
// Must apply backoff before closing the page, e.g. if CloudFlare is
@@ -67,16 +67,16 @@ async function lookupCard(browser: Browser, store: Store, page: Page, link: Link
const response: Response | null = await page.goto(link.url, {waitUntil: givenWaitFor});
if (!response) {
Logger.debug(Print.noResponse(link, store, true));
logger.debug(Print.noResponse(link, store, true));
}
const successStatusCodes = store.successStatusCodes ?? [[0, 399]];
const statusCode = response?.status() ?? 0;
if (!isStatusCodeInRange(statusCode, successStatusCodes)) {
if (statusCode === 429) {
Logger.warn(Print.rateLimit(link, store, true));
logger.warn(Print.rateLimit(link, store, true));
} else {
Logger.warn(Print.badStatusCode(link, store, statusCode, true));
logger.warn(Print.badStatusCode(link, store, statusCode, true));
}
return statusCode;
@@ -84,9 +84,9 @@ async function lookupCard(browser: Browser, store: Store, page: Page, link: Link
if (await lookupCardInStock(store, page, link)) {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
Logger.info(`${Print.inStock(link, store, true)}\n${givenUrl}`);
logger.info(`${Print.inStock(link, store, true)}\n${givenUrl}`);
if (Config.browser.open) {
if (config.browser.open) {
if (link.openCartAction === undefined) {
await open(givenUrl);
} else {
@@ -96,16 +96,16 @@ async function lookupCard(browser: Browser, store: Store, page: Page, link: Link
sendNotification(link, store);
if (Config.page.inStockWaitTime) {
if (config.page.inStockWaitTime) {
inStock[link.url] = true;
setTimeout(() => {
inStock[link.url] = false;
}, 1000 * Config.page.inStockWaitTime);
}, 1000 * config.page.inStockWaitTime);
}
if (Config.page.screenshot) {
Logger.debug(' saving screenshot');
if (config.page.screenshot) {
logger.debug(' saving screenshot');
link.screenshot = `success-${Date.now()}.png`;
await page.screenshot({path: link.screenshot});
@@ -126,36 +126,36 @@ async function lookupCardInStock(store: Store, page: Page, link: Link) {
const options = {...baseOptions, requireVisible: true, type: 'outerHTML' as const};
if (!await pageIncludesLabels(page, store.labels.inStock, options)) {
Logger.info(Print.outOfStock(link, store, true));
logger.info(Print.outOfStock(link, store, true));
return false;
}
}
if (store.labels.outOfStock) {
if (await pageIncludesLabels(page, store.labels.outOfStock, baseOptions)) {
Logger.info(Print.outOfStock(link, store, true));
logger.info(Print.outOfStock(link, store, true));
return false;
}
}
if (store.labels.bannedSeller) {
if (await pageIncludesLabels(page, store.labels.bannedSeller, baseOptions)) {
Logger.warn(Print.bannedSeller(link, store, true));
logger.warn(Print.bannedSeller(link, store, true));
return false;
}
}
if (store.labels.maxPrice) {
const priceLimit = await cardPriceLimit(page, store.labels.maxPrice, Config.store.maxPrice, baseOptions);
const priceLimit = await cardPriceLimit(page, store.labels.maxPrice, config.store.maxPrice, baseOptions);
if (priceLimit) {
Logger.info(Print.maxPrice(link, store, priceLimit, true));
logger.info(Print.maxPrice(link, store, priceLimit, true));
return false;
}
}
if (store.labels.captcha) {
if (await pageIncludesLabels(page, store.labels.captcha, baseOptions)) {
Logger.warn(Print.captcha(link, store, true));
logger.warn(Print.captcha(link, store, true));
await delay(getSleepTime());
return false;
}
@@ -165,14 +165,14 @@ async function lookupCardInStock(store: Store, page: Page, link: Link) {
}
export async function tryLookupAndLoop(browser: Browser, store: Store) {
Logger.debug(`[${store.name}] Starting lookup...`);
logger.debug(`[${store.name}] Starting lookup...`);
try {
await lookup(browser, store);
} catch (error) {
Logger.error(error);
logger.error(error);
}
const sleepTime = getSleepTime();
Logger.debug(`[${store.name}] Lookup done, next one in ${sleepTime} ms`);
logger.debug(`[${store.name}] Lookup done, next one in ${sleepTime} ms`);
setTimeout(tryLookupAndLoop, sleepTime, browser, store);
}
+6 -6
View File
@@ -1,7 +1,7 @@
import {Link, Store} from '..';
import {Logger, Print} from '../../../logger';
import {Print, logger} from '../../../logger';
import {delay, isStatusCodeInRange} from '../../../util';
import {Config} from '../../../config';
import {config} from '../../../config';
type Backoff = {
count: number;
@@ -27,26 +27,26 @@ export async function processBackoffDelay(store: Store, link: Link, statusCode:
let backoff = stores[store.name];
if (!backoff) {
backoff = {count: 0, time: Config.browser.minBackoff};
backoff = {count: 0, time: config.browser.minBackoff};
stores[store.name] = backoff;
}
if (!isBackoff) {
if (backoff.count > 0) {
backoff.count--;
backoff.time = Math.max(backoff.time / 2, Config.browser.minBackoff);
backoff.time = Math.max(backoff.time / 2, config.browser.minBackoff);
}
return -1;
}
const backoffTime = backoff.time;
Logger.debug(Print.backoff(link, store, {delay: backoffTime, statusCode}, true));
logger.debug(Print.backoff(link, store, {delay: backoffTime, statusCode}, true));
await delay(backoff.time);
backoff.count++;
backoff.time = Math.min(backoff.time * 2, Config.browser.maxBackoff);
backoff.time = Math.min(backoff.time * 2, config.browser.maxBackoff);
return backoffTime;
}
+17 -17
View File
@@ -1,8 +1,8 @@
import {NvidiaRegionInfo, regionInfos} from '../nvidia-api';
import {usingPage, usingResponse} from '../../../util';
import {Browser} from 'puppeteer';
import {Config} from '../../../config';
import {Logger} from '../../../logger';
import {config} from '../../../config';
import {logger} from '../../../logger';
import open from 'open';
interface NvidiaSessionTokenJSON {
@@ -34,7 +34,7 @@ export class NvidiaCart {
await this.refreshSessionToken();
setTimeout(callback, Config.nvidia.sessionTtl);
setTimeout(callback, config.nvidia.sessionTtl);
};
this.isKeepAlive = true;
@@ -47,7 +47,7 @@ export class NvidiaCart {
}
public get regionInfo(): NvidiaRegionInfo {
const country = Config.store.country;
const country = config.store.country;
const regionInfo = regionInfos.get(country);
if (!regionInfo) {
throw new Error(`Unknown country ${country}`);
@@ -62,20 +62,20 @@ export class NvidiaCart {
public async addToCard(productId: number, name: string): Promise<string> {
let cartUrl: string | undefined;
Logger.info(`🚀🚀🚀 [nvidia] ${name}, starting auto add to cart 🚀🚀🚀`);
logger.info(`🚀🚀🚀 [nvidia] ${name}, starting auto add to cart 🚀🚀🚀`);
try {
Logger.info(`🚀🚀🚀 [nvidia] ${name}, adding to cart 🚀🚀🚀`);
logger.info(`🚀🚀🚀 [nvidia] ${name}, adding to cart 🚀🚀🚀`);
let lastError: Error | string | undefined;
/* eslint-disable no-await-in-loop */
for (let i = 0; i < Config.nvidia.addToCardAttempts; i++) {
for (let i = 0; i < config.nvidia.addToCardAttempts; i++) {
try {
cartUrl = await this.addToCartAndGetLocationRedirect(productId);
break;
} catch (error) {
Logger.error(`✖ [nvidia] ${name} could not automatically add to cart, attempt ${i + 1} of ${Config.nvidia.addToCardAttempts}`, error);
Logger.debug(error);
logger.error(`✖ [nvidia] ${name} could not automatically add to cart, attempt ${i + 1} of ${config.nvidia.addToCardAttempts}`, error);
logger.debug(error);
lastError = error;
}
@@ -87,13 +87,13 @@ export class NvidiaCart {
throw lastError;
}
Logger.info(`🚀🚀🚀 [nvidia] ${name}, opening checkout page 🚀🚀🚀`);
Logger.info(cartUrl);
logger.info(`🚀🚀🚀 [nvidia] ${name}, opening checkout page 🚀🚀🚀`);
logger.info(cartUrl);
await open(cartUrl);
} catch (error) {
Logger.error(`✖ [nvidia] ${name} could not automatically add to cart, opening page`);
Logger.debug(error);
logger.error(`✖ [nvidia] ${name} could not automatically add to cart, opening page`);
logger.debug(error);
cartUrl = this.fallbackCartUrl;
@@ -116,7 +116,7 @@ export class NvidiaCart {
}
public async refreshSessionToken(): Promise<void> {
Logger.debug(' [nvidia] refreshing session token');
logger.debug(' [nvidia] refreshing session token');
try {
const result = await usingResponse(this.browser, this.sessionUrl, async response => {
return response?.json() as NvidiaSessionTokenJSON | undefined;
@@ -126,10 +126,10 @@ export class NvidiaCart {
}
this.sessionToken = result.session_token;
Logger.debug(` [nvidia] session_token=${result.session_token}`);
logger.debug(` [nvidia] session_token=${result.session_token}`);
} catch (error) {
const message: string = typeof error === 'object' ? error.message : error;
Logger.error(`✖ [nvidia] ${message}`);
logger.error(`✖ [nvidia] ${message}`);
}
}
@@ -137,7 +137,7 @@ export class NvidiaCart {
const url = 'https://api-prod.nvidia.com/direct-sales-shop/DR/add-to-cart';
const sessionToken = await this.getSessionToken();
Logger.info(` [nvidia] session_token=${sessionToken}`);
logger.info(` [nvidia] session_token=${sessionToken}`);
const locationData = await usingPage(this.browser, async page => {
page.removeAllListeners('request');
+3 -3
View File
@@ -1,12 +1,12 @@
import {NvidiaRegionInfo, regionInfos} from '../nvidia-api';
import {Browser} from 'puppeteer';
import {Config} from '../../../config';
import {Link} from '../store';
import {NvidiaCart} from './nvidia-cart';
import {config} from '../../../config';
import {timestampUrlParameter} from '../../timestamp-url-parameter';
function getRegionInfo(): NvidiaRegionInfo {
let country = Config.store.country;
let country = config.store.country;
if (!regionInfos.has(country)) {
country = 'usa';
}
@@ -30,7 +30,7 @@ export function generateSetupAction() {
return async (browser: Browser) => {
cart = new NvidiaCart(browser);
if (Config.browser.open) {
if (config.browser.open) {
cart.keepAlive();
}
};
+11 -11
View File
@@ -8,11 +8,9 @@ import {AsusDe} from './asus-de';
import {BAndH} from './bandh';
import {BestBuy} from './bestbuy';
import {BestBuyCa} from './bestbuy-ca';
import {Config} from '../../config';
import {Evga} from './evga';
import {EvgaEu} from './evga-eu';
import {Gamestop} from './gamestop';
import {Logger} from '../../logger';
import {MicroCenter} from './microcenter';
import {Newegg} from './newegg';
import {NeweggCa} from './newegg-ca';
@@ -22,6 +20,8 @@ import {OfficeDepot} from './officedepot';
import {Pny} from './pny';
import {Store} from './store';
import {Zotac} from './zotac';
import {config} from '../../config';
import {logger} from '../../logger';
const masterList = new Map([
[Adorama.name, Adorama],
@@ -49,27 +49,27 @@ const masterList = new Map([
const list = new Map();
for (const name of Config.store.stores) {
for (const name of config.store.stores) {
if (masterList.has(name)) {
list.set(name, masterList.get(name));
} else {
const logString = `No store named ${name}, skipping.`;
Logger.warn(logString);
logger.warn(logString);
}
}
Logger.info(` selected stores: ${Array.from(list.keys()).join(', ')}`);
logger.info(` selected stores: ${Array.from(list.keys()).join(', ')}`);
if (Config.store.showOnlyBrands.length > 0) {
Logger.info(` selected brands: ${Config.store.showOnlyBrands.join(', ')}`);
if (config.store.showOnlyBrands.length > 0) {
logger.info(` selected brands: ${config.store.showOnlyBrands.join(', ')}`);
}
if (Config.store.showOnlyModels.length > 0) {
Logger.info(` selected models: ${Config.store.showOnlyModels.join(', ')}`);
if (config.store.showOnlyModels.length > 0) {
logger.info(` selected models: ${config.store.showOnlyModels.join(', ')}`);
}
if (Config.store.showOnlySeries.length > 0) {
Logger.info(` selected series: ${Config.store.showOnlySeries.join(', ')}`);
if (config.store.showOnlySeries.length > 0) {
logger.info(` selected series: ${config.store.showOnlySeries.join(', ')}`);
}
export const Stores = Array.from(list.values()) as Store[];
+5 -4
View File
@@ -1,7 +1,8 @@
import {Config} from '../../config';
import {Store} from './store';
import {config} from '../../config';
const microCenterLocation = config.store.microCenterLocation;
const MicroCenterLocation = Config.store.microCenterLocation;
const microCenterLocationToId: Map<string, string> = new Map([
['web', '029'],
['brooklyn', '115'],
@@ -32,10 +33,10 @@ const microCenterLocationToId: Map<string, string> = new Map([
]);
let storeId: string;
if (microCenterLocationToId.get(MicroCenterLocation) === undefined) {
if (microCenterLocationToId.get(microCenterLocation) === undefined) {
storeId = '029';
} else {
storeId = microCenterLocationToId.get(MicroCenterLocation)!;
storeId = microCenterLocationToId.get(microCenterLocation)!;
}
export const MicroCenter: Store = {
+1 -1
View File
@@ -1 +1 @@
declare module 'pushbullet';
declare module '@hijef/pushbullet';
+7 -7
View File
@@ -1,11 +1,11 @@
import {Browser, Page, Response} from 'puppeteer';
import {Config} from './config';
import {Logger} from './logger';
import {StatusCodeRangeArray} from './store/model';
import {config} from './config';
import {disableBlockerInPage} from './adblocker';
import {logger} from './logger';
export function getSleepTime() {
return Config.browser.minSleep + (Math.random() * (Config.browser.maxSleep - Config.browser.minSleep));
return config.browser.minSleep + (Math.random() * (config.browser.maxSleep - config.browser.minSleep));
}
export async function delay(ms: number) {
@@ -47,8 +47,8 @@ export async function usingResponse<T>(
export async function usingPage<T>(browser: Browser, cb: (page: Page, browser: Browser) => Promise<T>): Promise<T> {
const page = await browser.newPage();
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
await page.setUserAgent(Config.page.userAgent);
page.setDefaultNavigationTimeout(config.page.timeout);
await page.setUserAgent(config.page.userAgent);
try {
return await cb(page, browser);
@@ -56,13 +56,13 @@ export async function usingPage<T>(browser: Browser, cb: (page: Page, browser: B
try {
await closePage(page);
} catch (error) {
Logger.error(error);
logger.error(error);
}
}
}
export async function closePage(page: Page) {
if (!Config.browser.lowBandwidth) {
if (!config.browser.lowBandwidth) {
await disableBlockerInPage(page);
}