mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 06:27:38 +00:00
feat: set country in config, login to nvidia when starting (#162)
This commit is contained in:
@@ -19,6 +19,7 @@ SHOW_ONLY_SERIES="3080"
|
|||||||
SLACK_CHANNEL="SlackChannelName"
|
SLACK_CHANNEL="SlackChannelName"
|
||||||
SLACK_TOKEN="slack-token"
|
SLACK_TOKEN="slack-token"
|
||||||
STORES="bestbuy,bandh,nvidia"
|
STORES="bestbuy,bandh,nvidia"
|
||||||
|
COUNTRY="usa"
|
||||||
SCREENSHOT="true"
|
SCREENSHOT="true"
|
||||||
TELEGRAM_ACCESS_TOKEN=""
|
TELEGRAM_ACCESS_TOKEN=""
|
||||||
TELEGRAM_CHAT_ID="1234"
|
TELEGRAM_CHAT_ID="1234"
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ Here is a list of variables that you can use to customize your newly copied `.en
|
|||||||
| `SLACK_CHANNEL` | Slack channel for posting | E.g., `update`, no need for `#` |
|
| `SLACK_CHANNEL` | Slack channel for posting | E.g., `update`, no need for `#` |
|
||||||
| `SLACK_TOKEN` | Slack API token |
|
| `SLACK_TOKEN` | Slack API token |
|
||||||
| `STORES` | [Supported stores](#supported-stores) you want to be scraped | Comma separated, default: `nvidia` |
|
| `STORES` | [Supported stores](#supported-stores) you want to be scraped | Comma separated, default: `nvidia` |
|
||||||
|
| `COUNTRY` | [Supported country](#supported-countries) you want to be scraped, currently only used by Nvidia | default: `usa` |
|
||||||
|
| `SCREENSHOT` | Capture screenshot of page if a card is found | Default: `true` |
|
||||||
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token |
|
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token |
|
||||||
| `TELEGRAM_CHAT_ID` | Telegram chat ID |
|
| `TELEGRAM_CHAT_ID` | Telegram chat ID |
|
||||||
| `USER_AGENT` | Custom User-Agent header for HTTP requests | Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` |
|
| `USER_AGENT` | Custom User-Agent header for HTTP requests | Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` |
|
||||||
@@ -124,6 +126,29 @@ Here is a list of variables that you can use to customize your newly copied `.en
|
|||||||
| T-Mobile | `tmobile`| |
|
| T-Mobile | `tmobile`| |
|
||||||
| Verizon | `verizon`| Works with Visible |
|
| Verizon | `verizon`| Works with Visible |
|
||||||
|
|
||||||
|
#### Supported countries
|
||||||
|
|
||||||
|
| **Country** | **Nvidia.com (3080 FE)** | **Nvidia.com (3090 FE)** | **Notes** |
|
||||||
|
|:---:|:---:|:---:|:---:|
|
||||||
|
| austria | `✔` | | |
|
||||||
|
| belgium | `✔` | | Nvidia supports debug |
|
||||||
|
| canada | `✔` | | |
|
||||||
|
| czechia | `✔` | | |
|
||||||
|
| denmark | `✔` | | |
|
||||||
|
| finland | `✔` | | |
|
||||||
|
| france | `✔` | | |
|
||||||
|
| germany | `✔` | | |
|
||||||
|
| great_britain | `✔` | | |
|
||||||
|
| ireland | `✔` | | |
|
||||||
|
| italy | `✔` | | |
|
||||||
|
| luxembourg | `✔` | | Nvidia supports debug |
|
||||||
|
| poland | `✔` | | |
|
||||||
|
| portugal | `✔` | | |
|
||||||
|
| russia | | | Missing all IDs |
|
||||||
|
| spain | `✔` | | |
|
||||||
|
| sweden | `✔` | | |
|
||||||
|
| usa | `✔` | | Nvidia supports debug |
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Q: What's Node.js and how do I install it?** Visit [their website](https://nodejs.org/en/) and download and install it. Very straight forward. Otherwise, Google more information related to your system needs.
|
**Q: What's Node.js and how do I install it?** Visit [their website](https://nodejs.org/en/) and download and install it. Very straight forward. Otherwise, Google more information related to your system needs.
|
||||||
|
|||||||
+2
-1
@@ -63,7 +63,8 @@ const page = {
|
|||||||
const store = {
|
const store = {
|
||||||
showOnlySeries: process.env.SHOW_ONLY_SERIES ? process.env.SHOW_ONLY_SERIES.split(',') : ['3070', '3080', '3090'],
|
showOnlySeries: process.env.SHOW_ONLY_SERIES ? process.env.SHOW_ONLY_SERIES.split(',') : ['3070', '3080', '3090'],
|
||||||
showOnlyBrands: process.env.SHOW_ONLY_BRANDS ? process.env.SHOW_ONLY_BRANDS.split(',') : [],
|
showOnlyBrands: process.env.SHOW_ONLY_BRANDS ? process.env.SHOW_ONLY_BRANDS.split(',') : [],
|
||||||
stores: process.env.STORES ? process.env.STORES.split(',') : ['nvidia']
|
stores: process.env.STORES ? process.env.STORES.split(',') : ['nvidia'],
|
||||||
|
country: process.env.COUNTRY ?? 'usa'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Config = {
|
export const Config = {
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ async function main() {
|
|||||||
|
|
||||||
for (const store of Stores) {
|
for (const store of Stores) {
|
||||||
Logger.debug(store.links);
|
Logger.debug(store.links);
|
||||||
|
if (store.setupAction !== undefined) {
|
||||||
|
store.setupAction(browser);
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(tryLookupAndLoop, getSleepTime(), browser, store);
|
setTimeout(tryLookupAndLoop, getSleepTime(), browser, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ async function lookup(browser: Browser, store: Store) {
|
|||||||
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
|
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
|
||||||
await page.setUserAgent(Config.page.userAgent);
|
await page.setUserAgent(Config.page.userAgent);
|
||||||
|
|
||||||
const graphicsCard = `${link.brand} ${link.model}`;
|
const graphicsCard = `${link.brand} ${link.model} ${link.series}`;
|
||||||
|
|
||||||
let response: Response | null;
|
let response: Response | null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import {timestampUrlParameter} from '../../timestamp-url-parameter';
|
||||||
|
import {Browser, Response} from 'puppeteer';
|
||||||
|
import {Logger} from '../../../logger';
|
||||||
|
import open from 'open';
|
||||||
|
import {Link} from '../store';
|
||||||
|
import {Config} from '../../../config';
|
||||||
|
import {NvidiaRegionInfo, regionInfos} from '../nvidia';
|
||||||
|
|
||||||
|
const nvidiaApiKey = '9485fa7b159e42edb08a83bde0d83dia';
|
||||||
|
|
||||||
|
function getRegionInfo(): NvidiaRegionInfo {
|
||||||
|
const country = Array.from(regionInfos.keys()).includes(Config.store.country) ? Config.store.country : 'usa';
|
||||||
|
|
||||||
|
const defaultRegionInfo: NvidiaRegionInfo = {drLocale: 'en_us', nvidiaLocale: 'en_us', fe3080Id: 5438481700, fe3090Id: null, fe2060SuperId: 5379432500};
|
||||||
|
return regionInfos.get(country) ?? defaultRegionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function digitalRiverStockUrl(id: number, drLocale: string): string {
|
||||||
|
return `https://api.digitalriver.com/v1/shoppers/me/products/${id}/inventory-status?` +
|
||||||
|
`&apiKey=${nvidiaApiKey}` +
|
||||||
|
`&locale=${drLocale}` +
|
||||||
|
timestampUrlParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NvidiaSessionTokenJSON {
|
||||||
|
access_token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nvidiaSessionUrl(nvidiaLocale: string): string {
|
||||||
|
return `https://store.nvidia.com/store/nvidia/SessionToken?format=json&locale=${nvidiaLocale}` +
|
||||||
|
`&apiKey=${nvidiaApiKey}` +
|
||||||
|
timestampUrlParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToCartUrl(id: number, drLocale: string, token: string): string {
|
||||||
|
return 'https://api.digitalriver.com/v1/shoppers/me/carts/active/line-items?format=json&method=post' +
|
||||||
|
`&productId=${id}` +
|
||||||
|
`&token=${token}` +
|
||||||
|
'&quantity=1' +
|
||||||
|
`&locale=${drLocale}` +
|
||||||
|
timestampUrlParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkoutUrl(drLocale: string, token: string): string {
|
||||||
|
return `https://api.digitalriver.com/v1/shoppers/me/carts/active/web-checkout?token=${token}&locale=${drLocale}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackCartUrl(nvidiaLocale: string): string {
|
||||||
|
return `https://www.nvidia.com/${nvidiaLocale}/shop/geforce?${timestampUrlParameter()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateSetupAction() {
|
||||||
|
return async (browser: Browser) => {
|
||||||
|
const {drLocale, nvidiaLocale} = getRegionInfo();
|
||||||
|
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
Logger.info('[nvidia] creating cart/session token...');
|
||||||
|
let response: Response | null;
|
||||||
|
try {
|
||||||
|
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
||||||
|
if (response === null) {
|
||||||
|
throw new Error('NvidiaAccessTokenUnavailable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json() as NvidiaSessionTokenJSON;
|
||||||
|
const accessToken = data.access_token;
|
||||||
|
|
||||||
|
Logger.info('[nvidia] you can log into your cart now...');
|
||||||
|
Logger.info(checkoutUrl(drLocale, accessToken));
|
||||||
|
await open(checkoutUrl(drLocale, accessToken));
|
||||||
|
} catch (error) {
|
||||||
|
Logger.debug(error);
|
||||||
|
Logger.error('✖ [nvidia] cannot generate cart/session token, continuing without, auto-"add to cart" may not work...');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateOpenCartAction(id: number, nvidiaLocale: string, drLocale: string, cardName: string) {
|
||||||
|
return async (browser: Browser) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, starting auto add to cart... 🚀🚀🚀`);
|
||||||
|
let response: Response | null;
|
||||||
|
try {
|
||||||
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, getting access token... 🚀🚀🚀`);
|
||||||
|
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
||||||
|
if (response === null) {
|
||||||
|
throw new Error('NvidiaAccessTokenUnavailable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json() as NvidiaSessionTokenJSON;
|
||||||
|
const accessToken = data.access_token;
|
||||||
|
|
||||||
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, adding to cart... 🚀🚀🚀`);
|
||||||
|
response = await page.goto(addToCartUrl(id, drLocale, accessToken), {waitUntil: 'networkidle0'});
|
||||||
|
|
||||||
|
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, opening checkout page... 🚀🚀🚀`);
|
||||||
|
Logger.info(checkoutUrl(drLocale, accessToken));
|
||||||
|
await open(checkoutUrl(drLocale, accessToken));
|
||||||
|
} catch (error) {
|
||||||
|
Logger.debug(error);
|
||||||
|
Logger.error(`✖ [nvidia] ${cardName} could not automatically add to cart, opening page`);
|
||||||
|
await open(fallbackCartUrl(nvidiaLocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateLinks(): Link[] {
|
||||||
|
const {drLocale, nvidiaLocale, fe3080Id, fe3090Id, fe2060SuperId} = getRegionInfo();
|
||||||
|
|
||||||
|
const links: Link[] = [];
|
||||||
|
|
||||||
|
if (fe2060SuperId) {
|
||||||
|
links.push({
|
||||||
|
series: 'debug',
|
||||||
|
brand: 'TEST',
|
||||||
|
model: 'CARD',
|
||||||
|
url: digitalRiverStockUrl(fe2060SuperId, drLocale),
|
||||||
|
openCartAction: generateOpenCartAction(fe2060SuperId, nvidiaLocale, drLocale, 'TEST CARD debug')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fe3080Id) {
|
||||||
|
links.push({
|
||||||
|
series: '3080',
|
||||||
|
brand: 'nvidia',
|
||||||
|
model: 'founders edition',
|
||||||
|
url: digitalRiverStockUrl(fe3080Id, drLocale),
|
||||||
|
openCartAction: generateOpenCartAction(fe3080Id, nvidiaLocale, drLocale, 'nvidia founders edition 3080')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fe3090Id) {
|
||||||
|
links.push({
|
||||||
|
series: '3090',
|
||||||
|
brand: 'nvidia',
|
||||||
|
model: 'founders edition',
|
||||||
|
url: digitalRiverStockUrl(fe3090Id, drLocale),
|
||||||
|
openCartAction: generateOpenCartAction(fe3090Id, nvidiaLocale, drLocale, 'nvidia founders edition 3090')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return links;
|
||||||
|
}
|
||||||
+31
-85
@@ -1,96 +1,42 @@
|
|||||||
import {Store} from './store';
|
import {Store} from './store';
|
||||||
import {Browser, Response} from 'puppeteer';
|
import {generateLinks, generateSetupAction} from './helpers/nvidia';
|
||||||
import {timestampUrlParameter} from '../timestamp-url-parameter';
|
|
||||||
import {Logger} from '../../logger';
|
|
||||||
import open from 'open';
|
|
||||||
|
|
||||||
const fe2060SuperId = 5379432500;
|
// Region/country set by config file, silently ignores null / missing values and defaults to usa
|
||||||
const fe3080Id = 5438481700;
|
|
||||||
const locale = 'en_us';
|
|
||||||
|
|
||||||
const nvidiaApiKey = '9485fa7b159e42edb08a83bde0d83dia';
|
export interface NvidiaRegionInfo {
|
||||||
|
drLocale: string;
|
||||||
function digitalRiverStockUrl(id: number): string {
|
nvidiaLocale: string;
|
||||||
return `https://api.digitalriver.com/v1/shoppers/me/products/${id}/inventory-status?` +
|
fe3080Id: number | null;
|
||||||
`&apiKey=${nvidiaApiKey}` +
|
fe3090Id: number | null;
|
||||||
timestampUrlParameter();
|
fe2060SuperId: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NvidiaSessionTokenJSON {
|
export const regionInfos = new Map<string, NvidiaRegionInfo>([
|
||||||
access_token: string;
|
['austria', {drLocale: 'de_de', nvidiaLocale: 'de_de', fe3080Id: 5440853700, fe3090Id: null, fe2060SuperId: null}],
|
||||||
}
|
['belgium', {drLocale: 'fr_fr', nvidiaLocale: 'fr_fr', fe3080Id: 5438795700, fe3090Id: null, fe2060SuperId: 5394902700}],
|
||||||
|
['canada', {drLocale: 'en_us', nvidiaLocale: 'en_ca', fe3080Id: 5438481700, fe3090Id: null, fe2060SuperId: null}],
|
||||||
function nvidiaSessionUrl(): string {
|
['czechia', {drLocale: 'en_gb', nvidiaLocale: 'en_gb', fe3080Id: 5438793800, fe3090Id: null, fe2060SuperId: null}],
|
||||||
return `https://store.nvidia.com/store/nvidia/SessionToken?format=json&locale=${locale}` +
|
['denmark', {drLocale: 'en_gb', nvidiaLocale: 'en_gb', fe3080Id: 5438793300, fe3090Id: null, fe2060SuperId: null}],
|
||||||
`&apiKey=${nvidiaApiKey}` +
|
['finland', {drLocale: 'en_gb', nvidiaLocale: 'en_gb', fe3080Id: 5438793300, fe3090Id: null, fe2060SuperId: null}],
|
||||||
timestampUrlParameter();
|
['france', {drLocale: 'fr_fr', nvidiaLocale: 'fr_fr', fe3080Id: 5438795200, fe3090Id: null, fe2060SuperId: null}],
|
||||||
}
|
['germany', {drLocale: 'de_de', nvidiaLocale: 'de_de', fe3080Id: 5438792300, fe3090Id: null, fe2060SuperId: null}],
|
||||||
|
['great_britain', {drLocale: 'en_gb', nvidiaLocale: 'en_gb', fe3080Id: 5438792800, fe3090Id: null, fe2060SuperId: null}],
|
||||||
function addToCartUrl(id: number, token: string): string {
|
['ireland', {drLocale: 'en_gb', nvidiaLocale: 'en_gb', fe3080Id: 5438792800, fe3090Id: null, fe2060SuperId: null}],
|
||||||
return 'https://api.digitalriver.com/v1/shoppers/me/carts/active/line-items?format=json&method=post' +
|
['italy', {drLocale: 'it_it', nvidiaLocale: 'it_it', fe3080Id: 5438796200, fe3090Id: null, fe2060SuperId: null}],
|
||||||
`&productId=${id}` +
|
['luxembourg', {drLocale: 'fr_fr', nvidiaLocale: 'fr_fr', fe3080Id: 5438795700, fe3090Id: null, fe2060SuperId: 5394902700}],
|
||||||
`&token=${token}` +
|
['poland', {drLocale: 'pl_pl', nvidiaLocale: 'pl_pl', fe3080Id: 5438797700, fe3090Id: null, fe2060SuperId: null}],
|
||||||
'&quantity=1' +
|
['portugal', {drLocale: 'en_gb', nvidiaLocale: 'en_gb', fe3080Id: 5438794300, fe3090Id: null, fe2060SuperId: null}],
|
||||||
timestampUrlParameter();
|
['russia', {drLocale: 'ru_ru', nvidiaLocale: 'ru_ru', fe3080Id: null, fe3090Id: null, fe2060SuperId: null}],
|
||||||
}
|
['spain', {drLocale: 'es_es', nvidiaLocale: 'es_es', fe3080Id: 5438794800, fe3090Id: null, fe2060SuperId: null}],
|
||||||
|
['sweden', {drLocale: 'sv_SE', nvidiaLocale: 'sv_se', fe3080Id: 5438798100, fe3090Id: null, fe2060SuperId: null}],
|
||||||
function checkoutUrl(token: string): string {
|
['usa', {drLocale: 'en_us', nvidiaLocale: 'en_us', fe3080Id: 5438481700, fe3090Id: null, fe2060SuperId: 5379432500}]
|
||||||
return `https://api.digitalriver.com/v1/shoppers/me/carts/active/web-checkout?token=${token}`;
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
function fallbackCartUrl(): string {
|
|
||||||
return `https://www.nvidia.com/en-us/shop/geforce?${timestampUrlParameter()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateCartAction(id: number, cardName: string) {
|
|
||||||
return async (browser: Browser) => {
|
|
||||||
const page = await browser.newPage();
|
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, starting auto add to cart... 🚀🚀🚀`);
|
|
||||||
let response: Response | null;
|
|
||||||
try {
|
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, getting access token... 🚀🚀🚀`);
|
|
||||||
response = await page.goto(nvidiaSessionUrl(), {waitUntil: 'networkidle0'});
|
|
||||||
if (response === null) {
|
|
||||||
throw new Error('NvidiaAccessTokenUnavailable');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json() as NvidiaSessionTokenJSON;
|
|
||||||
const accessToken = data.access_token;
|
|
||||||
|
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, adding to cart... 🚀🚀🚀`);
|
|
||||||
response = await page.goto(addToCartUrl(id, accessToken), {waitUntil: 'networkidle0'});
|
|
||||||
|
|
||||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, opening checkout page... 🚀🚀🚀`);
|
|
||||||
Logger.info(checkoutUrl(accessToken));
|
|
||||||
await open(checkoutUrl(accessToken));
|
|
||||||
} catch {
|
|
||||||
Logger.error(`✖ [nvidia] ${cardName} could not automatically add to cart, opening page`);
|
|
||||||
await open(fallbackCartUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Nvidia: Store = {
|
export const Nvidia: Store = {
|
||||||
links: [
|
links: generateLinks(),
|
||||||
{
|
|
||||||
series: 'debug',
|
|
||||||
brand: 'TEST',
|
|
||||||
model: 'CARD',
|
|
||||||
url: digitalRiverStockUrl(fe2060SuperId),
|
|
||||||
openCartAction: generateCartAction(fe2060SuperId, 'TEST CARD')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
series: '3080',
|
|
||||||
brand: 'nvidia',
|
|
||||||
model: 'founders edition',
|
|
||||||
url: digitalRiverStockUrl(fe3080Id),
|
|
||||||
openCartAction: generateCartAction(fe3080Id, 'nvidia founders edition 3080')
|
|
||||||
}
|
|
||||||
],
|
|
||||||
labels: {
|
labels: {
|
||||||
outOfStock: ['product_inventory_out_of_stock', 'rate limit exceeded', 'request timeout']
|
outOfStock: ['product_inventory_out_of_stock', 'rate limit exceeded', 'request timeout']
|
||||||
},
|
},
|
||||||
name: 'nvidia'
|
name: 'nvidia',
|
||||||
|
setupAction: generateSetupAction()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ export interface Store {
|
|||||||
links: Link[];
|
links: Link[];
|
||||||
labels: Labels;
|
labels: Labels;
|
||||||
name: string;
|
name: string;
|
||||||
|
setupAction?: (browser: Browser) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user