mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 02:57:34 +00:00
feat: invert logic (#141)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
This commit is contained in:
+87
-53
@@ -1,8 +1,8 @@
|
||||
import {Browser, Response} from 'puppeteer';
|
||||
import {Browser, Page, Response} from 'puppeteer';
|
||||
import {Link, Store} from './model';
|
||||
import {closePage, delay, getSleepTime} from '../util';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import {Store} from './model';
|
||||
import {includesLabels} from './includes-labels';
|
||||
import open from 'open';
|
||||
import {sendNotification} from '../notification';
|
||||
@@ -57,58 +57,10 @@ async function lookup(browser: Browser, store: Store) {
|
||||
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
|
||||
await page.setUserAgent(Config.page.userAgent);
|
||||
|
||||
const graphicsCard = `${link.brand} ${link.model} ${link.series}`;
|
||||
|
||||
let response: Response | null;
|
||||
try {
|
||||
response = await page.goto(link.url, {waitUntil: 'networkidle0'});
|
||||
} catch {
|
||||
Logger.error(`✖ [${store.name}] ${graphicsCard} skipping; timed out`);
|
||||
await closePage(page);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bodyHandle = await page.$('body');
|
||||
const textContent = await page.evaluate(body => body.textContent, bodyHandle);
|
||||
|
||||
Logger.debug(textContent);
|
||||
|
||||
if (includesLabels(textContent, store.labels.outOfStock)) {
|
||||
Logger.info(`✖ [${store.name}] still out of stock: ${graphicsCard}`);
|
||||
} else if (store.labels.bannedSeller && includesLabels(textContent, store.labels.bannedSeller)) {
|
||||
Logger.warn(`✖ [${store.name}] banned seller detected: ${graphicsCard}. skipping...`);
|
||||
} else if (store.labels.captcha && includesLabels(textContent, store.labels.captcha)) {
|
||||
Logger.warn(`✖ [${store.name}] CAPTCHA from: ${graphicsCard}. Waiting for a bit with this store...`);
|
||||
await delay(getSleepTime());
|
||||
} else if (response && response.status() === 429) {
|
||||
Logger.warn(`✖ [${store.name}] Rate limit exceeded: ${graphicsCard}`);
|
||||
} else {
|
||||
Logger.info(`🚀🚀🚀 [${store.name}] ${graphicsCard} IN STOCK 🚀🚀🚀`);
|
||||
Logger.info(link.url);
|
||||
if (Config.page.inStockWaitTime) {
|
||||
inStock[store.name] = true;
|
||||
setTimeout(() => {
|
||||
inStock[store.name] = false;
|
||||
}, 1000 * Config.page.inStockWaitTime);
|
||||
}
|
||||
|
||||
if (Config.page.capture) {
|
||||
Logger.debug('ℹ saving screenshot');
|
||||
link.screenshot = `success-${Date.now()}.png`;
|
||||
await page.screenshot({path: link.screenshot});
|
||||
}
|
||||
|
||||
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||
|
||||
if (Config.browser.open) {
|
||||
if (link.openCartAction === undefined) {
|
||||
await open(givenUrl);
|
||||
} else {
|
||||
link.openCartAction(browser);
|
||||
}
|
||||
}
|
||||
|
||||
sendNotification(givenUrl, link);
|
||||
await lookupCard(browser, store, page, link);
|
||||
} catch (error) {
|
||||
Logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`);
|
||||
}
|
||||
|
||||
await closePage(page);
|
||||
@@ -116,6 +68,88 @@ async function lookup(browser: Browser, store: Store) {
|
||||
/* eslint-enable no-await-in-loop */
|
||||
}
|
||||
|
||||
async function lookupCard(browser: Browser, store: Store, page: Page, link: Link) {
|
||||
const response: Response | null = await page.goto(link.url, {waitUntil: 'networkidle0'});
|
||||
const graphicsCard = `${link.brand} ${link.model}`;
|
||||
|
||||
if (await lookupCardInStock(store, page)) {
|
||||
Logger.info(`🚀🚀🚀 [${store.name}] ${graphicsCard} IN STOCK 🚀🚀🚀`);
|
||||
Logger.info(link.url);
|
||||
if (Config.page.inStockWaitTime) {
|
||||
inStock[store.name] = true;
|
||||
setTimeout(() => {
|
||||
inStock[store.name] = false;
|
||||
}, 1000 * Config.page.inStockWaitTime);
|
||||
}
|
||||
|
||||
if (Config.page.capture) {
|
||||
Logger.debug('ℹ saving screenshot');
|
||||
link.screenshot = `success-${Date.now()}.png`;
|
||||
await page.screenshot({path: link.screenshot});
|
||||
}
|
||||
|
||||
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||
|
||||
if (Config.browser.open) {
|
||||
if (link.openCartAction === undefined) {
|
||||
await open(givenUrl);
|
||||
} else {
|
||||
link.openCartAction(browser);
|
||||
}
|
||||
}
|
||||
|
||||
sendNotification(givenUrl, link);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await lookupPageHasCaptcha(store, page)) {
|
||||
Logger.warn(`✖ [${store.name}] CAPTCHA from: ${graphicsCard}. Waiting for a bit with this store...`);
|
||||
await delay(getSleepTime());
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && response.status() === 429) {
|
||||
Logger.warn(`✖ [${store.name}] Rate limit exceeded: ${graphicsCard}`);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info(`✖ [${store.name}] still out of stock: ${graphicsCard}`);
|
||||
}
|
||||
|
||||
async function lookupCardInStock(store: Store, page: Page) {
|
||||
const stockHandle = await page.$(store.labels.inStock.container);
|
||||
|
||||
const visible = await page.evaluate(element => element && element.offsetWidth > 0 && element.offsetHeight > 0, stockHandle);
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stockContent = await page.evaluate(element => element.outerHTML, stockHandle);
|
||||
|
||||
Logger.debug(stockContent);
|
||||
|
||||
if (includesLabels(stockContent, store.labels.inStock.text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function lookupPageHasCaptcha(store: Store, page: Page) {
|
||||
if (!store.labels.captcha) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const captchaHandle = await page.$(store.labels.captcha.container);
|
||||
const captchaContent = await page.evaluate(element => element.textContent, captchaHandle);
|
||||
|
||||
if (includesLabels(captchaContent, store.labels.captcha.text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function tryLookupAndLoop(browser: Browser, store: Store) {
|
||||
Logger.debug(`[${store.name}] Starting lookup...`);
|
||||
try {
|
||||
|
||||
@@ -2,8 +2,10 @@ import {Store} from './store';
|
||||
|
||||
export const Adorama: Store = {
|
||||
labels: {
|
||||
captcha: ['please verify you are a human'],
|
||||
outOfStock: ['temporarily not available', 'out of stock']
|
||||
inStock: {
|
||||
container: '.buy-section.purchase',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,8 +2,14 @@ import {Store} from './store';
|
||||
|
||||
export const AmazonCa: Store = {
|
||||
labels: {
|
||||
captcha: ['enter the characters you see below'],
|
||||
outOfStock: ['currently unavailable']
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['enter the characters you see below']
|
||||
},
|
||||
inStock: {
|
||||
container: '#desktop_buybox',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,9 +2,14 @@ import {Store} from './store';
|
||||
|
||||
export const Amazon: Store = {
|
||||
labels: {
|
||||
bannedSeller: ['sports authentics', 'raccoon capitalist', 'gigaparts'],
|
||||
captcha: ['enter the characters you see below'],
|
||||
outOfStock: ['currently unavailable', 'available from these sellers']
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['enter the characters you see below']
|
||||
},
|
||||
inStock: {
|
||||
container: '#desktop_buybox',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
+10
-1
@@ -2,9 +2,18 @@ import {Store} from './store';
|
||||
|
||||
export const Asus: Store = {
|
||||
labels: {
|
||||
outOfStock: ['coming soon', 'temporarily sold out']
|
||||
inStock: {
|
||||
container: '#item_add_cart',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'TEST',
|
||||
model: 'CARD',
|
||||
series: 'debug',
|
||||
url: 'https://store.asus.com/us/item/202003AM280000002/'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf oc',
|
||||
|
||||
@@ -2,7 +2,10 @@ import {Store} from './store';
|
||||
|
||||
export const BAndH: Store = {
|
||||
labels: {
|
||||
outOfStock: ['notify when available', 'try varying your search terms', 'sorry, an unexpected error has occurred']
|
||||
inStock: {
|
||||
container: 'div[data-selenium="addToCartSection"]',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
@@ -61,7 +64,6 @@ export const BAndH: Store = {
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html'
|
||||
}
|
||||
|
||||
],
|
||||
name: 'bandh'
|
||||
};
|
||||
|
||||
@@ -2,7 +2,10 @@ import {Store} from './store';
|
||||
|
||||
export const BestBuy: Store = {
|
||||
labels: {
|
||||
outOfStock: ['sold out', 'coming soon']
|
||||
inStock: {
|
||||
container: '.v-m-bottom-g',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,10 @@ import {Store} from './store';
|
||||
|
||||
export const EvgaEu: Store = {
|
||||
labels: {
|
||||
outOfStock: ['tbd', 'out of stock', 'error reaching the evga website', 'oops! something broke.']
|
||||
inStock: {
|
||||
container: '.product-buy-specs',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,10 @@ import {Store} from './store';
|
||||
|
||||
export const Evga: Store = {
|
||||
labels: {
|
||||
outOfStock: ['out of stock', 'error reaching the evga website', 'oops! something broke.']
|
||||
inStock: {
|
||||
container: '.product-buy-specs',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,10 @@ import {Store} from './store';
|
||||
|
||||
export const MicroCenter: Store = {
|
||||
labels: {
|
||||
outOfStock: ['sold out']
|
||||
inStock: {
|
||||
container: '#cart-options',
|
||||
text: ['(in stock)']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,8 +2,14 @@ import {Store} from './store';
|
||||
|
||||
export const NewEggCa: Store = {
|
||||
labels: {
|
||||
captcha: ['are you a human?'],
|
||||
outOfStock: ['auto notify', 'item is currently out of stock', 'service unavailable']
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['are you a human?']
|
||||
},
|
||||
inStock: {
|
||||
container: '#landingpage-cart .btn-primary span',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -2,8 +2,14 @@ import {Store} from './store';
|
||||
|
||||
export const NewEgg: Store = {
|
||||
labels: {
|
||||
captcha: ['are you a human?'],
|
||||
outOfStock: ['auto notify', 'item is currently out of stock', 'service unavailable', 'we are currently experiencing problems on our server']
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['are you a human?']
|
||||
},
|
||||
inStock: {
|
||||
container: '#landingpage-cart .btn-primary span',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -35,7 +35,10 @@ export const regionInfos = new Map<string, NvidiaRegionInfo>([
|
||||
|
||||
export const Nvidia: Store = {
|
||||
labels: {
|
||||
outOfStock: ['product_inventory_out_of_stock', 'rate limit exceeded', 'request timeout']
|
||||
inStock: {
|
||||
container: 'body',
|
||||
text: ['product_inventory_in_stock']
|
||||
}
|
||||
},
|
||||
links: generateLinks(),
|
||||
name: 'nvidia',
|
||||
|
||||
@@ -2,15 +2,21 @@ import {Store} from './store';
|
||||
|
||||
export const OfficeDepot: Store = {
|
||||
labels: {
|
||||
captcha: ['please verify you are a human'],
|
||||
outOfStock: ['out of stock for delivery', 'out of stock', 'we are unable to process your last request']
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['please verify you are a human']
|
||||
},
|
||||
inStock: {
|
||||
container: '#productPurchase',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'TEST',
|
||||
model: 'CARD',
|
||||
series: 'debug',
|
||||
url: 'https://www.officedepot.com/a/products/7189374/PNY-GeForce-RTX-3080-10GB-GDDR6X/'
|
||||
url: 'https://www.officedepot.com/a/products/4652239/EVGA-GeForce-RTX-2060-Graphic-Card/'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import {Browser} from 'puppeteer';
|
||||
|
||||
export interface Element {
|
||||
container: string;
|
||||
text: string[];
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
series: string;
|
||||
brand: string;
|
||||
@@ -11,9 +16,8 @@ export interface Link {
|
||||
}
|
||||
|
||||
export interface Labels {
|
||||
outOfStock: string[];
|
||||
captcha?: string[];
|
||||
bannedSeller?: string[];
|
||||
captcha?: Element;
|
||||
inStock: Element;
|
||||
}
|
||||
|
||||
export interface Store {
|
||||
|
||||
@@ -2,9 +2,18 @@ import {Store} from './store';
|
||||
|
||||
export const Zotac: Store = {
|
||||
labels: {
|
||||
outOfStock: ['out of stock', 'this process is automatic']
|
||||
inStock: {
|
||||
container: '.add-to-cart-wrapper',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'TEST',
|
||||
model: 'CARD',
|
||||
series: 'debug',
|
||||
url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-2060-twin-fan-zt-t20600f-10m'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
|
||||
Reference in New Issue
Block a user