mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 15:47:35 +00:00
chore(misc): normalize logs (#242)
This commit is contained in:
+1
-1
@@ -60,7 +60,7 @@
|
|||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "npm run lint && npm run build"
|
"pre-commit": "npm run lint"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
import {Link} from '../store/model';
|
import {Link, Store} from '../store/model';
|
||||||
import {sendNotification} from '../notification';
|
import {sendNotification} from '../notification';
|
||||||
|
|
||||||
const link: Link = {
|
const link: Link = {
|
||||||
brand: 'brand',
|
brand: 'test:brand',
|
||||||
cartUrl: 'http://example.com/',
|
cartUrl: 'test:cartUrl',
|
||||||
model: 'model',
|
model: 'test:model',
|
||||||
series: 'debug',
|
series: 'test:series',
|
||||||
url: 'http://example.com/'
|
url: 'test:url'
|
||||||
|
};
|
||||||
|
|
||||||
|
const store: Store = {
|
||||||
|
labels: {
|
||||||
|
inStock: {
|
||||||
|
container: 'test:container',
|
||||||
|
text: ['test:text']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
links: [link],
|
||||||
|
name: 'test:name'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send test email.
|
* Send test email.
|
||||||
*/
|
*/
|
||||||
sendNotification(link.cartUrl ?? link.url, link);
|
sendNotification(link, store);
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ const browser = {
|
|||||||
isTrusted: process.env.BROWSER_TRUSTED ? process.env.BROWSER_TRUSTED === 'true' : false,
|
isTrusted: process.env.BROWSER_TRUSTED ? process.env.BROWSER_TRUSTED === 'true' : false,
|
||||||
maxSleep: Number(process.env.PAGE_SLEEP_MAX ?? 10000),
|
maxSleep: Number(process.env.PAGE_SLEEP_MAX ?? 10000),
|
||||||
minSleep: Number(process.env.PAGE_SLEEP_MIN ?? 5000),
|
minSleep: Number(process.env.PAGE_SLEEP_MIN ?? 5000),
|
||||||
open: process.env.OPEN_BROWSER === 'true'
|
open: process.env.OPEN_BROWSER ? process.env.OPEN_BROWSER === 'true' : true
|
||||||
};
|
};
|
||||||
|
|
||||||
const logLevel = process.env.LOG_LEVEL ?? 'info';
|
const logLevel = process.env.LOG_LEVEL ?? 'info';
|
||||||
|
|||||||
+2
-3
@@ -15,7 +15,7 @@ puppeteer.use(adBlocker);
|
|||||||
*/
|
*/
|
||||||
async function main() {
|
async function main() {
|
||||||
if (Stores.length === 0) {
|
if (Stores.length === 0) {
|
||||||
Logger.error('No stores selected.');
|
Logger.error('✖ no stores selected', Stores);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,6 @@ async function main() {
|
|||||||
try {
|
try {
|
||||||
void main();
|
void main();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignoring errors; more than likely due to rate limits
|
Logger.error('✖ something bad happened, resetting nvidia-snatcher', error);
|
||||||
Logger.error(error);
|
|
||||||
void main();
|
void main();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {Link, Store} from './store/model';
|
||||||
import winston, {format} from 'winston';
|
import winston, {format} from 'winston';
|
||||||
import {Config} from './config';
|
import {Config} from './config';
|
||||||
|
|
||||||
@@ -22,3 +23,18 @@ export const Logger = winston.createLogger({
|
|||||||
level: Config.logLevel,
|
level: Config.logLevel,
|
||||||
transports: [new winston.transports.Console({})]
|
transports: [new winston.transports.Console({})]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const Print = {
|
||||||
|
captcha(link: Link, store: Store): string {
|
||||||
|
return `✖ [${store.name}] [${link.brand} (${link.series})] ${link.model} :: CAPTCHA`;
|
||||||
|
},
|
||||||
|
inStock(link: Link, store: Store): string {
|
||||||
|
return `🚀🚨 [${store.name}] [${link.brand} (${link.series})] ${link.model} :: IN STOCK 🚨🚀`;
|
||||||
|
},
|
||||||
|
outOfStock(link: Link, store: Store): string {
|
||||||
|
return `✖ [${store.name}] [${link.brand} (${link.series})] ${link.model} :: OUT OF STOCK`;
|
||||||
|
},
|
||||||
|
rateLimit(link: Link, store: Store): string {
|
||||||
|
return `✖ [${store.name}] [${link.brand} (${link.series})] ${link.model} :: RATE LIMIT EXCEEDED`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import {Link} from '../store/model';
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import notifier from 'node-notifier';
|
import notifier from 'node-notifier';
|
||||||
|
|
||||||
export function sendDesktopNotification(cartUrl: string, link: Link) {
|
export function sendDesktopNotification(link: Link, store: Store) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const title = link.brand + ' ' + link.model + ' IN STOCK';
|
|
||||||
const message = cartUrl;
|
|
||||||
|
|
||||||
notifier.notify({
|
notifier.notify({
|
||||||
message,
|
message: link.cartUrl ? link.cartUrl : link.url,
|
||||||
title
|
title: Print.inStock(link, store)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Logger.info('✔ desktop notification sent');
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
import {MessageBuilder, Webhook} from 'discord-webhook-node';
|
import {MessageBuilder, Webhook} from 'discord-webhook-node';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Link} from '../store/model';
|
|
||||||
import {Logger} from '../logger';
|
import {Logger} from '../logger';
|
||||||
|
|
||||||
const hook = new Webhook(Config.notifications.discord.webHookUrl);
|
const hook = new Webhook(Config.notifications.discord.webHookUrl);
|
||||||
const notifyGroup = Config.notifications.discord.notifyGroup;
|
const notifyGroup = Config.notifications.discord.notifyGroup;
|
||||||
|
|
||||||
export function sendDiscordMessage(cartUrl: string, link: Link) {
|
export function sendDiscordMessage(link: Link, store: Store) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const embed = new MessageBuilder();
|
const embed = new MessageBuilder();
|
||||||
embed.setTitle('Stock Notification');
|
embed.setTitle('Stock Notification');
|
||||||
embed.addField('URL', cartUrl, true);
|
embed.addField('URL', link.cartUrl ? link.cartUrl : link.url, true);
|
||||||
|
embed.addField('Store', store.name, true);
|
||||||
embed.addField('Brand', link.brand, true);
|
embed.addField('Brand', link.brand, true);
|
||||||
embed.addField('Model', link.model, true);
|
embed.addField('Model', link.model, true);
|
||||||
|
|
||||||
@@ -22,9 +23,10 @@ export function sendDiscordMessage(cartUrl: string, link: Link) {
|
|||||||
embed.setColor(0x76B900);
|
embed.setColor(0x76B900);
|
||||||
embed.setTimestamp();
|
embed.setTimestamp();
|
||||||
await hook.send(embed);
|
await hook.send(embed);
|
||||||
Logger.info(`↗ discord message sent: ${cartUrl}`);
|
|
||||||
|
Logger.info('✔ discord message sent');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error);
|
Logger.error('✖ couldn\'t send discord message', error);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-20
@@ -1,11 +1,10 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Link} from '../store/model';
|
|
||||||
import {Logger} from '../logger';
|
|
||||||
import Mail from 'nodemailer/lib/mailer';
|
import Mail from 'nodemailer/lib/mailer';
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
|
|
||||||
const email = Config.notifications.email;
|
const email = Config.notifications.email;
|
||||||
const subject = 'NVIDIA - BUY NOW';
|
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
auth: {
|
auth: {
|
||||||
@@ -15,30 +14,25 @@ const transporter = nodemailer.createTransport({
|
|||||||
service: 'gmail'
|
service: 'gmail'
|
||||||
});
|
});
|
||||||
|
|
||||||
const mailOptions: Mail.Options = {
|
export function sendEmail(link: Link, store: Store) {
|
||||||
from: email.username,
|
const mailOptions: Mail.Options = {
|
||||||
subject,
|
attachments: link.screenshot ? [
|
||||||
to: email.username
|
|
||||||
};
|
|
||||||
|
|
||||||
export function sendEmail(cartUrl: string, link: Link) {
|
|
||||||
mailOptions.text = cartUrl;
|
|
||||||
|
|
||||||
if (link.screenshot) {
|
|
||||||
mailOptions.attachments = [
|
|
||||||
{
|
{
|
||||||
filename: link.screenshot,
|
filename: link.screenshot,
|
||||||
path: `./${link.screenshot}`
|
path: `./${link.screenshot}`
|
||||||
}
|
}
|
||||||
];
|
] : undefined,
|
||||||
}
|
from: email.username,
|
||||||
|
subject: Print.inStock(link, store),
|
||||||
|
text: link.cartUrl ? link.cartUrl : link.url,
|
||||||
|
to: email.username
|
||||||
|
};
|
||||||
|
|
||||||
transporter.sendMail(mailOptions, (error, info) => {
|
transporter.sendMail(mailOptions, error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
Logger.error(error);
|
Logger.error('✖ couldn\'t send email', error);
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
Logger.info('✔ email sent');
|
||||||
Logger.info(`↗ email sent: ${info.response}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Link} from '../store/model';
|
import {Logger} from '../logger';
|
||||||
import {playSound} from './sound';
|
import {playSound} from './sound';
|
||||||
import {sendDesktopNotification} from './desktop';
|
import {sendDesktopNotification} from './desktop';
|
||||||
import {sendDiscordMessage} from './discord';
|
import {sendDiscordMessage} from './discord';
|
||||||
@@ -13,44 +14,53 @@ import {sendTweet} from './twitter';
|
|||||||
|
|
||||||
const notifications = Config.notifications;
|
const notifications = Config.notifications;
|
||||||
|
|
||||||
export function sendNotification(cartUrl: string, link: Link) {
|
export function sendNotification(link: Link, store: Store) {
|
||||||
if (notifications.email.username && notifications.email.password) {
|
if (notifications.email.username && notifications.email.password) {
|
||||||
sendEmail(cartUrl, link);
|
Logger.debug('↗ sending email');
|
||||||
}
|
sendEmail(link, store);
|
||||||
|
|
||||||
if (notifications.slack.channel && notifications.slack.token) {
|
|
||||||
sendSlackMessage(cartUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
|
|
||||||
sendTelegramMessage(cartUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifications.discord.webHookUrl) {
|
|
||||||
sendDiscordMessage(cartUrl, link);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifications.phone.number) {
|
if (notifications.phone.number) {
|
||||||
|
Logger.debug('↗ sending sms');
|
||||||
const carrier = notifications.phone.carrier.toLowerCase();
|
const carrier = notifications.phone.carrier.toLowerCase();
|
||||||
if (carrier && notifications.phone.availableCarriers.has(carrier)) {
|
if (carrier && notifications.phone.availableCarriers.has(carrier)) {
|
||||||
sendSMS(cartUrl, link);
|
sendSMS(link, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifications.pushBulletApiKey) {
|
|
||||||
sendPushBulletNotification(cartUrl, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifications.pushover.token && notifications.pushover.username) {
|
|
||||||
sendPushoverNotification(cartUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifications.playSound) {
|
if (notifications.playSound) {
|
||||||
|
Logger.debug('↗ playing sound');
|
||||||
playSound();
|
playSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifications.desktop) {
|
if (notifications.desktop) {
|
||||||
sendDesktopNotification(cartUrl, link);
|
Logger.debug('↗ sending desktop notification');
|
||||||
|
sendDesktopNotification(link, store);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifications.discord.webHookUrl) {
|
||||||
|
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.pushBulletApiKey) {
|
||||||
|
Logger.debug('↗ sending pushbullet message');
|
||||||
|
sendPushBulletNotification(link, store);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifications.pushover.token && notifications.pushover.username) {
|
||||||
|
Logger.debug('↗ sending pushover message');
|
||||||
|
sendPushoverNotification(link, store);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -59,6 +69,7 @@ export function sendNotification(cartUrl: string, link: Link) {
|
|||||||
notifications.twitter.consumerKey &&
|
notifications.twitter.consumerKey &&
|
||||||
notifications.twitter.consumerSecret
|
notifications.twitter.consumerSecret
|
||||||
) {
|
) {
|
||||||
sendTweet(cartUrl, link);
|
Logger.debug('↗ sending twitter message');
|
||||||
|
sendTweet(link, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Link} from '../store/model';
|
|
||||||
import {Logger} from '../logger';
|
|
||||||
import PushBullet from 'pushbullet';
|
import PushBullet from 'pushbullet';
|
||||||
|
|
||||||
const pushBulletApiKey = Config.notifications.pushBulletApiKey;
|
const pushBulletApiKey = Config.notifications.pushBulletApiKey;
|
||||||
|
|
||||||
export function sendPushBulletNotification(cartUrl: string, link: Link) {
|
export function sendPushBulletNotification(link: Link, store: Store) {
|
||||||
const pusher = new PushBullet(pushBulletApiKey);
|
const pusher = new PushBullet(pushBulletApiKey);
|
||||||
const title = `🚨 ${link.brand} ${link.model} ${link.series} 👀`;
|
|
||||||
|
|
||||||
pusher.note({}, title, cartUrl, (err: Error, result: string) => {
|
pusher.note(
|
||||||
if (err) {
|
{},
|
||||||
Logger.error(err);
|
Print.inStock(link, store),
|
||||||
} else {
|
link.cartUrl ? link.cartUrl : link.url,
|
||||||
Logger.info(`↗ pushbullet notification sent: ${result}`);
|
(error: Error) => {
|
||||||
}
|
if (error) {
|
||||||
});
|
Logger.error('✖ couldn\'t send pushbullet message', error);
|
||||||
|
} else {
|
||||||
|
Logger.info('✔ pushbullet message sent');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Logger} from '../logger';
|
|
||||||
import Push from 'pushover-notifications';
|
import Push from 'pushover-notifications';
|
||||||
|
|
||||||
const pushover = Config.notifications.pushover;
|
const pushover = Config.notifications.pushover;
|
||||||
@@ -8,16 +9,17 @@ const push = new Push({
|
|||||||
user: pushover.username
|
user: pushover.username
|
||||||
});
|
});
|
||||||
|
|
||||||
export function sendPushoverNotification(cartUrl: string) {
|
export function sendPushoverNotification(link: Link, store: Store) {
|
||||||
const message = {
|
const message = {
|
||||||
message: cartUrl
|
message: link.cartUrl ? link.cartUrl : link.url,
|
||||||
|
title: Print.inStock(link, store)
|
||||||
};
|
};
|
||||||
|
|
||||||
push.send(message, (err: Error, result: string) => {
|
push.send(message, (error: Error) => {
|
||||||
if (err) {
|
if (error) {
|
||||||
Logger.error(err);
|
Logger.error('✖ couldn\'t send pushover message', error);
|
||||||
} else {
|
} else {
|
||||||
Logger.info(`↗ pushover notification sent: ${result}`);
|
Logger.info('✔ pushover message sent');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Logger} from '../logger';
|
|
||||||
import {WebClient} from '@slack/web-api';
|
import {WebClient} from '@slack/web-api';
|
||||||
|
|
||||||
const channel = Config.notifications.slack.channel;
|
const channel = Config.notifications.slack.channel;
|
||||||
const token = Config.notifications.slack.token;
|
const token = Config.notifications.slack.token;
|
||||||
const web = new WebClient(token);
|
const web = new WebClient(token);
|
||||||
|
|
||||||
export function sendSlackMessage(cartUrl: string) {
|
export function sendSlackMessage(link: Link, store: Store) {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await web.chat.postMessage({channel, text: cartUrl});
|
const result = await web.chat.postMessage({
|
||||||
|
channel,
|
||||||
|
text: `${Print.inStock(link, store)}\n${givenUrl}`
|
||||||
|
});
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
Logger.error(result.error);
|
Logger.error('✖ couldn\'t send slack message', result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info(`↗ slack message sent to '${channel}': ${cartUrl}`);
|
Logger.info('✔ slack message sent');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error);
|
Logger.error('✖ couldn\'t send slack message', error);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-20
@@ -1,10 +1,9 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Link} from '../store/model';
|
|
||||||
import {Logger} from '../logger';
|
|
||||||
import Mail from 'nodemailer/lib/mailer';
|
import Mail from 'nodemailer/lib/mailer';
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
|
|
||||||
const subject = 'NVIDIA - BUY NOW';
|
|
||||||
const [email, phone] = [Config.notifications.email, Config.notifications.phone];
|
const [email, phone] = [Config.notifications.email, Config.notifications.phone];
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
@@ -15,37 +14,35 @@ const transporter = nodemailer.createTransport({
|
|||||||
service: 'gmail'
|
service: 'gmail'
|
||||||
});
|
});
|
||||||
|
|
||||||
const mailOptions: Mail.Options = {
|
export function sendSMS(link: Link, store: Store) {
|
||||||
from: Config.notifications.email.username,
|
const mailOptions: Mail.Options = {
|
||||||
subject,
|
attachments: link.screenshot ? [
|
||||||
to: generateAddress()
|
|
||||||
};
|
|
||||||
|
|
||||||
export function sendSMS(text: string, link: Link) {
|
|
||||||
mailOptions.text = text;
|
|
||||||
|
|
||||||
if (link.screenshot) {
|
|
||||||
mailOptions.attachments = [
|
|
||||||
{
|
{
|
||||||
filename: link.screenshot,
|
filename: link.screenshot,
|
||||||
path: `./${link.screenshot}`
|
path: `./${link.screenshot}`
|
||||||
}
|
}
|
||||||
];
|
] : undefined,
|
||||||
}
|
from: email.username,
|
||||||
|
subject: Print.inStock(link, store),
|
||||||
|
text: link.cartUrl ? link.cartUrl : link.url,
|
||||||
|
to: generateAddress()
|
||||||
|
};
|
||||||
|
|
||||||
transporter.sendMail(mailOptions, (error, info) => {
|
transporter.sendMail(mailOptions, error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
Logger.error(error);
|
Logger.error('✖ couldn\'t send sms', error);
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
Logger.info('✔ sms sent');
|
||||||
Logger.info(`↗ sms sent: ${info.response}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateAddress() {
|
function generateAddress() {
|
||||||
const carrier = phone.carrier.toLowerCase();
|
const carrier = phone.carrier.toLowerCase();
|
||||||
|
|
||||||
if (carrier && phone.availableCarriers.has(carrier)) {
|
if (carrier && phone.availableCarriers.has(carrier)) {
|
||||||
return [phone.number, phone.availableCarriers.get(carrier)].join('@');
|
return [phone.number, phone.availableCarriers.get(carrier)].join('@');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.error('✖ unknown carrier', carrier);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -5,29 +5,29 @@ import playerLib from 'play-sound';
|
|||||||
|
|
||||||
const notificationSound = Config.notifications.playSound;
|
const notificationSound = Config.notifications.playSound;
|
||||||
|
|
||||||
Logger.info('Searching for sound player...');
|
Logger.info('ℹ searching for sound player...');
|
||||||
const player = playerLib();
|
const player = playerLib();
|
||||||
if (player.player === null) {
|
if (player.player === null) {
|
||||||
Logger.warn('No sound player found.');
|
Logger.warn('No sound player found.');
|
||||||
} else {
|
} else {
|
||||||
const playerName: string = player.player;
|
const playerName: string = player.player;
|
||||||
Logger.info(`Sound player found: ${playerName}`);
|
Logger.info(`✔ sound player found: ${playerName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function playSound() {
|
export function playSound() {
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
fs.access(notificationSound, fs.constants.F_OK, err => {
|
fs.access(notificationSound, fs.constants.F_OK, error => {
|
||||||
if (err) {
|
if (error) {
|
||||||
Logger.error(`error opening sound file: ${err.message}`);
|
Logger.error(`✖ error opening sound file: ${error.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.play(notificationSound, (err: string) => {
|
player.play(notificationSound, (error: Error) => {
|
||||||
Logger.info('↗ playing sound');
|
if (error) {
|
||||||
|
Logger.error('✖ couldn\'t play sound', error);
|
||||||
if (err) {
|
|
||||||
Logger.error(`error playing sound: ${err}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.info('✔ played sound');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Logger} from '../logger';
|
|
||||||
import {TelegramClient} from 'messaging-api-telegram';
|
import {TelegramClient} from 'messaging-api-telegram';
|
||||||
|
|
||||||
const telegram = Config.notifications.telegram;
|
const telegram = Config.notifications.telegram;
|
||||||
@@ -8,13 +9,15 @@ const client = new TelegramClient({
|
|||||||
accessToken: telegram.accessToken
|
accessToken: telegram.accessToken
|
||||||
});
|
});
|
||||||
|
|
||||||
export function sendTelegramMessage(text: string) {
|
export function sendTelegramMessage(link: Link, store: Store) {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.sendMessage(telegram.chatId, text);
|
await client.sendMessage(telegram.chatId, `${Print.inStock(link, store)}\n${givenUrl}`);
|
||||||
Logger.info(`↗ telegram message sent to '${telegram.chatId}': ${text}`);
|
Logger.info('✔ telegram message sent');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error);
|
Logger.error('✖ couldn\'t send telegram message', error);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import {Link, Store} from '../store/model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Link} from '../store/model';
|
|
||||||
import {Logger} from '../logger';
|
|
||||||
import Twitter from 'twitter';
|
import Twitter from 'twitter';
|
||||||
|
|
||||||
const twitter = Config.notifications.twitter;
|
const twitter = Config.notifications.twitter;
|
||||||
@@ -12,18 +12,18 @@ const client = new Twitter({
|
|||||||
consumer_secret: twitter.consumerSecret
|
consumer_secret: twitter.consumerSecret
|
||||||
});
|
});
|
||||||
|
|
||||||
export function sendTweet(cartUrl: string, link: Link) {
|
export function sendTweet(link: Link, store: Store) {
|
||||||
let status = `🛎️ Stock Notification: ${link.brand} ${link.model}\n${cartUrl}`;
|
let status = `${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`;
|
||||||
|
|
||||||
if (twitter.tweetTags) {
|
if (twitter.tweetTags) {
|
||||||
status += `\n\n${twitter.tweetTags}`;
|
status += `\n\n${twitter.tweetTags}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.post('statuses/update', {status}, err => {
|
client.post('statuses/update', {status}, error => {
|
||||||
if (err) {
|
if (error) {
|
||||||
Logger.error(err);
|
Logger.error('✖ couldn\'t send twitter notification', error);
|
||||||
} else {
|
} else {
|
||||||
Logger.info(`↗ twitter notification sent: ${cartUrl}`);
|
Logger.info('✔ twitter notification sent');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-22
@@ -1,8 +1,8 @@
|
|||||||
import {Browser, Page, Response} from 'puppeteer';
|
import {Browser, Page, Response} from 'puppeteer';
|
||||||
import {Link, Store} from './model';
|
import {Link, Store} from './model';
|
||||||
|
import {Logger, Print} from '../logger';
|
||||||
import {closePage, delay, getSleepTime} from '../util';
|
import {closePage, delay, getSleepTime} from '../util';
|
||||||
import {Config} from '../config';
|
import {Config} from '../config';
|
||||||
import {Logger} from '../logger';
|
|
||||||
import {includesLabels} from './includes-labels';
|
import {includesLabels} from './includes-labels';
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
import {sendNotification} from '../notification';
|
import {sendNotification} from '../notification';
|
||||||
@@ -69,27 +69,12 @@ async function lookup(browser: Browser, store: Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function lookupCard(browser: Browser, store: Store, page: Page, link: Link) {
|
async function lookupCard(browser: Browser, store: Store, page: Page, link: Link) {
|
||||||
const givenWaitFor = store.customWaitFor ? store.customWaitFor : 'networkidle0';
|
const givenWaitFor = store.waitUntil ? store.waitUntil : 'networkidle0';
|
||||||
const response: Response | null = await page.goto(link.url, {waitUntil: givenWaitFor});
|
const response: Response | null = await page.goto(link.url, {waitUntil: givenWaitFor});
|
||||||
const graphicsCard = `${link.brand} ${link.model}`;
|
|
||||||
|
|
||||||
if (await lookupCardInStock(store, page)) {
|
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});
|
|
||||||
}
|
|
||||||
|
|
||||||
let givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
let givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||||
|
Logger.info(`${Print.inStock(link, store)}\n${givenUrl}`);
|
||||||
|
|
||||||
if (Config.browser.open) {
|
if (Config.browser.open) {
|
||||||
if (link.openCartAction === undefined) {
|
if (link.openCartAction === undefined) {
|
||||||
@@ -99,22 +84,38 @@ async function lookupCard(browser: Browser, store: Store, page: Page, link: Link
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNotification(givenUrl, link);
|
sendNotification(link, store);
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await lookupPageHasCaptcha(store, page)) {
|
if (await lookupPageHasCaptcha(store, page)) {
|
||||||
Logger.warn(`✖ [${store.name}] CAPTCHA from: ${graphicsCard}. Waiting for a bit with this store...`);
|
Logger.warn(Print.captcha(link, store));
|
||||||
await delay(getSleepTime());
|
await delay(getSleepTime());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response && response.status() === 429) {
|
if (response && response.status() === 429) {
|
||||||
Logger.warn(`✖ [${store.name}] Rate limit exceeded: ${graphicsCard}`);
|
Logger.warn(Print.rateLimit(link, store));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info(`✖ [${store.name}] still out of stock: ${graphicsCard}`);
|
Logger.info(Print.outOfStock(link, store));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function lookupCardInStock(store: Store, page: Page) {
|
async function lookupCardInStock(store: Store, page: Page) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {Store} from './store';
|
import {Store} from './store';
|
||||||
|
|
||||||
export const BestBuyCa: Store = {
|
export const BestBuyCa: Store = {
|
||||||
customWaitFor: 'domcontentloaded',
|
|
||||||
labels: {
|
labels: {
|
||||||
inStock: {
|
inStock: {
|
||||||
container: '#root',
|
container: '#root',
|
||||||
@@ -22,5 +21,6 @@ export const BestBuyCa: Store = {
|
|||||||
url: 'https://www.bestbuy.ca/en-ca/product/zotac-geforce-rtx-3080-trinity-10gb-gddr6x-video-card/14953249?intl=nosplash'
|
url: 'https://www.bestbuy.ca/en-ca/product/zotac-geforce-rtx-3080-trinity-10gb-gddr6x-video-card/14953249?intl=nosplash'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
name: 'bestbuy-ca'
|
name: 'bestbuy-ca',
|
||||||
|
waitUntil: 'domcontentloaded'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,26 +55,29 @@ export function generateSetupAction() {
|
|||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
||||||
Logger.info('[nvidia] creating cart/session token...');
|
|
||||||
let response: Response | null;
|
let response: Response | null;
|
||||||
try {
|
try {
|
||||||
|
Logger.debug('creating cart/session token...');
|
||||||
|
|
||||||
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
||||||
|
|
||||||
if (response === null) {
|
if (response === null) {
|
||||||
throw new Error('NvidiaAccessTokenUnavailable');
|
throw new Error('NvidiaAccessTokenUnavailable');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json() as NvidiaSessionTokenJSON;
|
const data = await response.json() as NvidiaSessionTokenJSON;
|
||||||
const accessToken = data.access_token;
|
const accessToken = data.access_token;
|
||||||
|
|
||||||
Logger.info('[nvidia] you can log into your cart now...');
|
|
||||||
const cartUrl = checkoutUrl(drLocale, accessToken);
|
const cartUrl = checkoutUrl(drLocale, accessToken);
|
||||||
Logger.info(cartUrl);
|
|
||||||
|
Logger.debug(cartUrl);
|
||||||
|
|
||||||
if (Config.browser.open) {
|
if (Config.browser.open) {
|
||||||
|
Logger.info('ℹ opening browser for user to login');
|
||||||
|
|
||||||
await open(cartUrl);
|
await open(cartUrl);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.debug(error);
|
Logger.error('✖ [nvidia] cannot generate cart/session token, continuing without; auto "add to cart" may not work', error);
|
||||||
Logger.error('✖ [nvidia] cannot generate cart/session token, continuing without, auto-"add to cart" may not work...');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.close();
|
await page.close();
|
||||||
@@ -84,11 +87,14 @@ export function generateSetupAction() {
|
|||||||
export function generateOpenCartAction(id: number, nvidiaLocale: string, drLocale: string, cardName: string) {
|
export function generateOpenCartAction(id: number, nvidiaLocale: string, drLocale: string, cardName: string) {
|
||||||
return async (browser: Browser) => {
|
return async (browser: Browser) => {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, starting auto add to cart... 🚀🚀🚀`);
|
|
||||||
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, starting auto add to cart 🚀🚀🚀`);
|
||||||
|
|
||||||
let response: Response | null;
|
let response: Response | null;
|
||||||
let cartUrl: string;
|
let cartUrl: string;
|
||||||
try {
|
try {
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, getting access token... 🚀🚀🚀`);
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, getting access token 🚀🚀🚀`);
|
||||||
|
|
||||||
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
||||||
if (response === null) {
|
if (response === null) {
|
||||||
throw new Error('NvidiaAccessTokenUnavailable');
|
throw new Error('NvidiaAccessTokenUnavailable');
|
||||||
@@ -97,16 +103,21 @@ export function generateOpenCartAction(id: number, nvidiaLocale: string, drLocal
|
|||||||
const data = await response.json() as NvidiaSessionTokenJSON;
|
const data = await response.json() as NvidiaSessionTokenJSON;
|
||||||
const accessToken = data.access_token;
|
const accessToken = data.access_token;
|
||||||
|
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, adding to cart... 🚀🚀🚀`);
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, adding to cart 🚀🚀🚀`);
|
||||||
|
|
||||||
response = await page.goto(addToCartUrl(id, drLocale, accessToken), {waitUntil: 'networkidle0'});
|
response = await page.goto(addToCartUrl(id, drLocale, accessToken), {waitUntil: 'networkidle0'});
|
||||||
|
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, opening checkout page... 🚀🚀🚀`);
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, opening checkout page 🚀🚀🚀`);
|
||||||
|
|
||||||
cartUrl = checkoutUrl(drLocale, accessToken);
|
cartUrl = checkoutUrl(drLocale, accessToken);
|
||||||
|
|
||||||
Logger.info(cartUrl);
|
Logger.info(cartUrl);
|
||||||
|
|
||||||
await open(cartUrl);
|
await open(cartUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.debug(error);
|
Logger.debug(error);
|
||||||
Logger.error(`✖ [nvidia] ${cardName} could not automatically add to cart, opening page`);
|
Logger.error(`✖ [nvidia] ${cardName} could not automatically add to cart, opening page`, error);
|
||||||
|
|
||||||
cartUrl = fallbackCartUrl(nvidiaLocale);
|
cartUrl = fallbackCartUrl(nvidiaLocale);
|
||||||
await open(cartUrl);
|
await open(cartUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ for (const name of Config.store.stores) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logString = `Selected stores: ${Array.from(list.keys()).join(', ')}`;
|
Logger.info(`ℹ selected stores: ${Array.from(list.keys()).join(', ')}`);
|
||||||
Logger.info(logString);
|
|
||||||
|
|
||||||
export const Stores = Array.from(list.values()) as Store[];
|
export const Stores = Array.from(list.values()) as Store[];
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {Browser, LoadEvent} from 'puppeteer';
|
import {Browser, LoadEvent} from 'puppeteer';
|
||||||
|
|
||||||
export interface Element {
|
export type Element = {
|
||||||
container: string;
|
container: string;
|
||||||
text: string[];
|
text: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface Link {
|
export type Link = {
|
||||||
series: string;
|
series: string;
|
||||||
brand: string;
|
brand: string;
|
||||||
model: string;
|
model: string;
|
||||||
@@ -13,17 +13,17 @@ export interface Link {
|
|||||||
cartUrl?: string;
|
cartUrl?: string;
|
||||||
openCartAction?: (browser: Browser) => Promise<string>;
|
openCartAction?: (browser: Browser) => Promise<string>;
|
||||||
screenshot?: string;
|
screenshot?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface Labels {
|
export type Labels = {
|
||||||
captcha?: Element;
|
captcha?: Element;
|
||||||
inStock: Element;
|
inStock: Element;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface Store {
|
export type Store = {
|
||||||
links: Link[];
|
links: Link[];
|
||||||
labels: Labels;
|
labels: Labels;
|
||||||
name: string;
|
name: string;
|
||||||
setupAction?: (browser: Browser) => void;
|
setupAction?: (browser: Browser) => void;
|
||||||
customWaitFor?: LoadEvent;
|
waitUntil?: LoadEvent;
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user