mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 00:38:43 +00:00
feat(docs): add mkdocs
This commit is contained in:
+8
-3
@@ -1,12 +1,17 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{yml, json, md}]
|
||||
[*.md]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
indent_size = 4
|
||||
|
||||
[*.ts]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Python runtime
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Install Python dependencies
|
||||
run: pip install mkdocs-material
|
||||
- name: Deploy documentation
|
||||
run: mkdocs gh-deploy --force
|
||||
@@ -1,562 +1,48 @@
|
||||
<p align="center"><a href="https://github.com/jef/streetmerchant#readme"><img src="https://raw.githubusercontent.com/jef/streetmerchant/main/media/streetmerchant.png" alt="streetmerchant" /></a></p>
|
||||
<p align="center">The world's easiest, most powerful stock checker
|
||||
<br/><br/>
|
||||
<a href="https://github.com/jef/streetmerchant/actions?query=workflow%3Aci"><img src="https://github.com/jef/streetmerchant/workflows/ci/badge.svg" /></a>
|
||||
<a href="https://discord.gg/gbVY4vB9JF"><img src="https://img.shields.io/discord/773913070665859073.svg?label=chat&logo=discord&logoColor=ffffff&color=7389D8" alt="Tweet" /></a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Beat%20the%20masses%20with%20streetmerchant&url=https://github.com/jef/streetmerchant&hashtags=typescript,opensource,bot,shopping"><img src="https://img.shields.io/badge/twitter-share-green?logo=twitter&style=social" alt="Tweet" /></a>
|
||||
<br/><br/>
|
||||
<a href="https://github.com/jef/streetmerchant#faq">FAQ</a> |
|
||||
<a href="https://github.com/jef/streetmerchant/issues">Issues</a> |
|
||||
<a href="https://github.com/jef/streetmerchant/wiki">Wiki</a>
|
||||
<p align="center">
|
||||
<a href="https://jef.codes/streetmerchant"
|
||||
><img
|
||||
src="https://raw.githubusercontent.com/jef/streetmerchant/main/media/streetmerchant.png"
|
||||
alt="streetmerchant"
|
||||
/></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>The world's easiest, most powerful stock checker</strong>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/jef/streetmerchant/actions?query=workflow%3Aci"
|
||||
><img src="https://github.com/jef/streetmerchant/workflows/ci/badge.svg"
|
||||
/></a>
|
||||
<a href="https://discord.gg/gbVY4vB9JF"
|
||||
><img
|
||||
src="https://img.shields.io/discord/773913070665859073.svg?label=chat&logo=discord&logoColor=ffffff&color=7389D8"
|
||||
alt="Tweet"
|
||||
/></a>
|
||||
<a
|
||||
href="https://twitter.com/intent/tweet?text=Beat%20the%20masses%20with%20streetmerchant&url=https://github.com/jef/streetmerchant&hashtags=typescript,opensource,bot,shopping"
|
||||
><img
|
||||
src="https://img.shields.io/badge/twitter-share-green?logo=twitter&style=social"
|
||||
alt="Tweet"
|
||||
/></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>To get started, visit <a href="https://jef.codes/streetmerchant">jef.codes/streetmerchant</a></em>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
- Scrapes multiple websites for patterns of being stocked via API and Chromium
|
||||
- Opens browser when stock is available
|
||||
- Ability to send notifications when stock is available
|
||||
First and foremost, this service _will not_ automatically buy for you.
|
||||
|
||||
> :point_right: The bot _will not_ automatically buy for you
|
||||
- **Checks stock continuously** -- runs 24/7, 365, looking for the items you want.
|
||||
- **Ready for checkout** -- ability to add to cart when available and even opens the browser for you.
|
||||
- **Notifications galore** -- when you're not by your computer, worry free with notifications to most platforms and devices when an item comes in stock.
|
||||
|
||||
## Installation overview
|
||||
## Quick start
|
||||
|
||||
Linux, macOS, and Windows are all capable operating systems.
|
||||
streetmerchant can ran with Node.js 14:
|
||||
|
||||
You do not need any computer skills, smarts, or anything of that nature. You are very capable as you have made it this
|
||||
far. Some basic understanding how a terminal, git, and or Node.js is a bonus, but that does not limit you to
|
||||
getting `streetmerchant` running!
|
||||
|
||||
### Installation: native
|
||||
|
||||
| Reference | Note |
|
||||
|:---:|---|
|
||||
| tag | Example, `v1.0.0`; stable |
|
||||
| `main` | Latest HEAD; not tagged, could be unstable |
|
||||
|
||||
- [Node.js 14](https://nodejs.org/en/)
|
||||
- [git](https://git-scm.com/)
|
||||
- Clone this project `git clone https://github.com/jef/streetmerchant.git`
|
||||
- To checkout a particular ref, use `git checkout <ref name>` after cloning
|
||||
- Navigate to this project by entering `cd streetmerchant`
|
||||
- Run `npm install`
|
||||
- Copy `.env-example` to a new file `.env` and edit the `.env` file to your liking using
|
||||
your [favorite text editor](https://code.visualstudio.com/)
|
||||
- More on this in [customization](#Customization)
|
||||
- Run `npm run start` to start
|
||||
|
||||
At any point you want the program to stop, use <kbd>Ctrl</kbd> + <kbd>C</kbd>.
|
||||
|
||||
> :point_right: Please visit the [wiki](https://github.com/jef/streetmerchant/wiki) if you need more help with installation.
|
||||
|
||||
### Installation: Docker
|
||||
|
||||
Available via GitHub Container Registry.
|
||||
|
||||
| Tag | Note |
|
||||
|:---:|---|
|
||||
| `latest` | Latest release; stable |
|
||||
| `nightly` | Latest HEAD each day at midnight UTC; could be unstable |
|
||||
|
||||
```sh
|
||||
# to run
|
||||
docker run --cap-add=SYS_ADMIN \
|
||||
-it --rm --env-file ./.env \
|
||||
ghcr.io/jef/streetmerchant:nightly
|
||||
|
||||
# to test notifications
|
||||
docker run --cap-add=SYS_ADMIN \
|
||||
-it --rm --env-file ./.env \
|
||||
ghcr.io/jef/streetmerchant:nightly test:notification:production
|
||||
```shell
|
||||
git clone https://github.com/jef/streetmerchant.git
|
||||
cd streetmerchant && npm i && npm run start
|
||||
```
|
||||
|
||||
### Developer notes
|
||||
|
||||
The command `npm run start:dev` can be used instead of `npm run start` to automatically restart the project when
|
||||
filesystem changes are detected in the `src/` folder or `.env` file.
|
||||
|
||||
## Customization
|
||||
|
||||
To customize `streetmerchant`, make a copy of `.env-example` as `.env` and make any changes to your liking. _All
|
||||
environment variables are **optional**._
|
||||
|
||||
<details>
|
||||
<summary>Expand to see all available options</summary>
|
||||
|
||||
### Application
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `AUTO_ADD_TO_CART` | Enable auto add to cart on support stores | Default: `true` |
|
||||
| `BROWSER_TRUSTED` | Skip Chromium Sandbox | Useful for containerized environments, default: `false` |
|
||||
| `HEADLESS` | Puppeteer to run headless or not | Debugging related, default: `true` |
|
||||
| `INCOGNITO` | Puppeteer to run incognito or not | Debugging related, default: `false` |
|
||||
| `IN_STOCK_WAIT_TIME` | Time to wait between requests to the same link if it has that card in stock | In seconds, default: `0` |
|
||||
| `LOG_LEVEL` | [Logging levels](https://github.com/winstonjs/winston#logging-levels) | Debugging related, default: `info` |
|
||||
| `LOW_BANDWIDTH` | Blocks images/fonts to reduce traffic | Disables ad blocker, default: `false` |
|
||||
| `OPEN_BROWSER` | Toggle for whether or not the browser should open when item is found | Default: `true` |
|
||||
| `PAGE_BACKOFF_MIN` | Minimum backoff time between retrying requests for the same store when a forbidden response is received | Default: `10000` |
|
||||
| `PAGE_BACKOFF_MAX` | Maximum backoff time between retrying requests for the same store when a forbidden response is received | Default: `3600000` |
|
||||
| `PAGE_SLEEP_MIN` | Minimum sleep time between queries of the same product page | In milliseconds, default: `5000` |
|
||||
| `PAGE_SLEEP_MAX` | Maximum sleep time between queries of the same product page | In milliseconds, default: `10000` |
|
||||
| `PAGE_TIMEOUT` | Navigation Timeout in milliseconds | `0` for infinite, default: `30000` |
|
||||
| `PROXY_PROTOCOL` | protocol of proxy server, such as `socks5` | default: `http` |
|
||||
| `PROXY_ADDRESS` | IP Address or fqdn of proxy server |
|
||||
| `PROXY_PORT` | TCP Port number on which the proxy is listening for connections | Default: `80` |
|
||||
| `SCREENSHOT` | Capture screenshot of page if a card is found | Default: `true` |
|
||||
| `USER_AGENT` | Custom User-Agents headers for HTTP requests | Newline separated, e.g.: `USER_AGENT_STRING1 \n USER_AGENT_STRING2` | | Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` |
|
||||
| `WEB_PORT` | Starts a webserver to be able to control the bot while it is running; optional | Default: disabled |
|
||||
|
||||
> :point_right: You can find your computer's user agent by [searching google for "my user agent"](http://google.com/search?q=my+user+agent)
|
||||
|
||||
> :point_right: Data usage is [known to be high](https://github.com/jef/streetmerchant/issues?q=is%3Aissue+sort%3Aupdated-desc+bandwidth). This is expected as the program scrapes many websites in parallel 24/7. To help reduce this, use `LOW_BANDWIDTH="true"`. We are looking into other solutions as well, but is low priority.
|
||||
|
||||
### Filters
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `COUNTRY` | [Supported country](#supported-countries) you want to be scraped | Only used with `nvidia-api`, default: `usa` |
|
||||
| `MAX_PRICE_SERIES_3060TI` | Maximum price allowed for a match, applies 3060 Ti series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_3070` | Maximum price allowed for a match, applies 3070 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_3080` | Maximum price allowed for a match, applies 3080 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_3090` | Maximum price allowed for a match, applies 3090 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_CORSAIR_SF` | Maximum price allowed for a match, applies to Corsair PSUs | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PSUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5600` | Maximum price allowed for a match, applies AMD 5600 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5800` | Maximum price allowed for a match, applies AMD 5800 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5900` | Maximum price allowed for a match, applies AMD 5900 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5950` | Maximum price allowed for a match, applies AMD 5950 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_SONYPS5C` | Maximum price allowed for a match, applies PS5 console | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PS5 above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_SONYPS5DE` | Maximum price allowed for a match, applies PS5 digital edition | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PS5 above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_TEST` | Maximum price allowed for a match, applies `test:series` | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PS5 above `1234` will be skipped. |
|
||||
| `MICROCENTER_LOCATION` | Specific MicroCenter location(s) to search | Comma separated, e.g.: `marietta,duluth`, default: `web` |
|
||||
| `NVIDIA_ADD_TO_CART_ATTEMPTS` | The maximum number of times the `nvidia-api` add to cart feature will be attempted before failing | Default: `10` |
|
||||
| `NVIDIA_SESSION_TTL` | The time in milliseconds to keep the cart active while using `nvidia-api` | Default: `60000` |
|
||||
| `SHOW_ONLY_BRANDS` | Filter to show specified brands | Comma separated, e.g.: `evga,zotac` |
|
||||
| `SHOW_ONLY_MODELS` | Filter to show specified models | Both supported formats are comma separated <br/><br/>1. Standard E.g.: `founders edition,rog strix` <br/><br/> 2. Advanced E.g: `MODEL:SERIES`, E.g: `founders edition:3090,rog strix` |
|
||||
| `SHOW_ONLY_SERIES` | Filter to show specified series | Comma separated, e.g.: `3080,ryzen5900` |
|
||||
| `STORES` | [Supported stores](#supported-stores) you want to be scraped | Both supported formats are comma separated <br/><br/>1. Standard E.g.: `"nvidia"` <br/><br/> 2. Advanced E.g: `STORE:PAGE_SLEEP_MIN:PAGE_SLEEP_MAX`, E.g: `nvidia:10000:30000` <br/><br/>Default: `nvidia` |
|
||||
|
||||
<details>
|
||||
<summary>Supported stores</summary>
|
||||
|
||||
> :point_right: Used with the `STORES` variable.
|
||||
|
||||
| Stores | Environment variable |
|
||||
|:---:|:---:|
|
||||
| Adorama | `adorama`|
|
||||
| Alternate (DE) | `alternate`|
|
||||
| Alternate (NL) | `alternate-nl`|
|
||||
| Amazon | `amazon`|
|
||||
| Amazon (CA) | `amazon-ca`|
|
||||
| Amazon (DE) | `amazon-de`|
|
||||
| Amazon (DE) Warehouse | `amazon-de-warehouse`|
|
||||
| Amazon (ES) | `amazon-es`|
|
||||
| Amazon (FR) | `amazon-fr`|
|
||||
| Amazon (IT) | `amazon-it`|
|
||||
| Amazon (NL) | `amazon-nl`|
|
||||
| Amazon (UK) | `amazon-uk`|
|
||||
| AMD | `amd`|
|
||||
| AMD (CA) | `amd-ca`|
|
||||
| AMD (DE) | `amd-de`|
|
||||
| AMD (IT) | `amd-it`|
|
||||
| AntOnline | `antonline`|
|
||||
| Argos (UK) | `argos`|
|
||||
| Aria PC (UK) | `aria`|
|
||||
| ARLT (DE) | `arlt`|
|
||||
| ASUS | `asus` |
|
||||
| ASUS (DE) | `asus-de` |
|
||||
| Azerty (NL) | `azerty`|
|
||||
| B&H | `bandh`|
|
||||
| Best Buy | `bestbuy`|
|
||||
| Best Buy (CA) | `bestbuy-ca`|
|
||||
| Box (UK) | `box`|
|
||||
| CanadaComputers (CA) | `canadacomputers` |
|
||||
| Caseking (DE) | `caseking`|
|
||||
| CCL (UK) | `ccl`|
|
||||
| Comet (IT) | `comet`|
|
||||
| Computeruniverse (DE) | `computeruniverse` |
|
||||
| Coolblue (NL) | `coolblue`|
|
||||
| Coolmod (ES) | `coolmod`|
|
||||
| Corsair | `corsair`|
|
||||
| Currys (UK) | `currys`|
|
||||
| Cyberport (DE) | `cyberport` |
|
||||
| eBuyer (UK) | `ebuyer`|
|
||||
| El Corte Inglés | `elcorteingles`|
|
||||
| ePrice (IT) | `eprice`|
|
||||
| Euronics (IT) | `euronics`|
|
||||
| Euronics (DE) | `euronics-de`|
|
||||
| EVGA | `evga`|
|
||||
| EVGA (EU) | `evga-eu`|
|
||||
| Expert | `expert`|
|
||||
| Galaxus (DE) | `galaxus`|
|
||||
| Game (UK) | `game`|
|
||||
| Gamestop | `gamestop`|
|
||||
| Gamestop (DE) | `gamestop-de`|
|
||||
| Kabum (BR) | `kabum`|
|
||||
| Mediamarkt (DE) | `mediamarkt`|
|
||||
| Medimax | `medimax`|
|
||||
| MemoryExpress (CA) | `memoryexpress`|
|
||||
| Micro Center | `microcenter`|
|
||||
| Mindfactory (DE) | `mindfactory` |
|
||||
| Newegg | `newegg`|
|
||||
| Newegg (CA) | `newegg-ca`|
|
||||
| Notebooksbilliger (DE) |`notebooksbilliger`|
|
||||
| Novatech (UK) | `novatech`|
|
||||
| Nvidia | `nvidia`|
|
||||
| Nvidia (API) | `nvidia-api`|
|
||||
| Office Depot | `officedepot`|
|
||||
| Otto | `otto`|
|
||||
| Overclockers (UK) | `overclockers`|
|
||||
| PCComponentes (ES) | `pccomponentes`|
|
||||
| PlayStation | `playstation`|
|
||||
| PNY | `pny`|
|
||||
| Proshop (DE) | `proshop-de`|
|
||||
| Proshop (DK) | `proshop-dk`|
|
||||
| Saturn (DE) | `saturn`|
|
||||
| Scan (UK) | `scan`|
|
||||
| Smyths Toys (UK) | `smythstoys`|
|
||||
| Spielegrotte | `spielegrotte`|
|
||||
| Target | `target`|
|
||||
| Unieuro (IT) | `unieuro`|
|
||||
| Very (UK) | `very`|
|
||||
| Walmart | `walmart`|
|
||||
| Wipoid | `wipoid`|
|
||||
| Zotac | `zotac`|
|
||||
| TopAchat | `topachat`|
|
||||
|
||||
<details>
|
||||
<summary>Micro Center stores</summary>
|
||||
|
||||
> :point_right: Used with the `MICROCENTER_LOCATION` variable.
|
||||
|
||||
> :point_right: Before using `web`, please review [this issue comment](https://github.com/jef/streetmerchant/issues/442#issuecomment-703297393).
|
||||
|
||||
| Store name |
|
||||
|:---:|
|
||||
| `brooklyn` |
|
||||
| `brentwood` |
|
||||
| `cambridge` |
|
||||
| `chicago` |
|
||||
| `columbus` |
|
||||
| `dallas` |
|
||||
| `denver` |
|
||||
| `duluth` |
|
||||
| `fairfax` |
|
||||
| `flushing` |
|
||||
| `houston` |
|
||||
| `madison-heights` |
|
||||
| `marietta` |
|
||||
| `mayfield-heights` |
|
||||
| `north-jersey` |
|
||||
| `overland-park` |
|
||||
| `parkville` |
|
||||
| `rockville` |
|
||||
| `sharonville` |
|
||||
| `st-davids` |
|
||||
| `st-louis-park` |
|
||||
| `tustin` |
|
||||
| `westbury` |
|
||||
| `westmont` |
|
||||
| `yonkers` |
|
||||
|
||||
</details>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Supported brands and models</summary>
|
||||
|
||||
> :point_right: Used with the `SHOW_ONLY_BRANDS` and `SHOW_ONLY_MODELS` variables.
|
||||
|
||||
| Brand | Model |
|
||||
|:---:|---|
|
||||
| `amd` | `5600x`, `5800x`, `5900x`, `5950x`, `amd reference` |
|
||||
| `asus` | `dual`, `dual oc`, `strix`, `strix oc`, `tuf`, `tuf oc` |
|
||||
| `corsair` | `750 platinum`, `600 platinum` |
|
||||
| `evga` | `ftw3`, `ftw3 ultra`, `xc3`, `xc3 black`, `xc3 ultra` |
|
||||
| `gainward` | `phantom gs`, `phoenix`, `phoenix gs`, `phoenix gs oc` |
|
||||
| `gigabyte` | `aorus master`, `aorus xtreme`, `eagle`, `eagle oc`, `gaming`, `gaming oc`, `turbo`, `vision`, `vision oc` |
|
||||
| `inno3d` | `gaming x3`, `ichill x3`, `ichill x4`, `twin x2 oc` |
|
||||
| `kfa2` | `sg`, `sg oc` |
|
||||
| `microsoft` | `xbox series x`, `xbox series s` |
|
||||
| `msi` | `gaming x trio`, `ventus 2x oc`, `ventus 3x`, `ventus 3x oc` |
|
||||
| `nvidia` | `founders edition` |
|
||||
| `palit` | `gamerock oc`, `gaming pro`, `gaming pro oc` |
|
||||
| `pny` | `dual fan`, `xlr8 revel`, `xlr8 uprising` |
|
||||
| `sony` | `ps5 console`, `ps5 digital` |
|
||||
| `xfx` | `merc`, `amd reference` |
|
||||
| `zotac` | `amp holo`, `amp extreme holo`, `trinity`, `trinity oc`, `twin edge`, `twin edge oc` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Supported series</summary>
|
||||
|
||||
> :point_right: Used with the `SHOW_ONLY_SERIES` variable.
|
||||
|
||||
| Series | Environment variable |
|
||||
|:---:|:---:|
|
||||
| `AMD Ryzen 5600x` | `ryzen5600` |
|
||||
| `AMD Ryzen 5800x` | `ryzen5800` |
|
||||
| `AMD Ryzen 5900x` | `ryzen5900` |
|
||||
| `AMD Ryzen 5950x` | `ryzen5950` |
|
||||
| `AMD RX 6800` | `rx6800` |
|
||||
| `AMD RX 6800XT` | `rx6800xt` |
|
||||
| `AMD RX 6900XT` | `rx6900xt` |
|
||||
| `Nvidia RTX 3060 Ti` | `3060ti` |
|
||||
| `Nvidia RTX 3070` | `3070` |
|
||||
| `Nvidia RTX 3080` | `3080` |
|
||||
| `Nvidia RTX 3090` | `3090` |
|
||||
| `Corsair SFX PSU` | `sf` |
|
||||
| `Sony PS5` | `sonyps5c` |
|
||||
| `Sony PS5 Digital Edition` | `sonyps5de` |
|
||||
| `Xbox Series S` | `xboxss` |
|
||||
| `Xbox Series X` | `xboxsx` |
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Supported countries (used with nvidia and nvidia-api)</summary>
|
||||
|
||||
> :point_right: Used with the `COUNTRY` variable.
|
||||
|
||||
| Country | 3080 FE | 3090 FE | Test Card | Notes |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| austria | `✔` | `✔` | `✔` | |
|
||||
| belgium | `✔` | `✔` | `✔` | |
|
||||
| canada | `✔` | `✔` | `✔` | |
|
||||
| czechia | `✔` | `✔` | `✔` | |
|
||||
| denmark | `✔` | | `✔` | Missing RTX 3090 |
|
||||
| finland | `✔` | | `✔` | Missing RTX 3090 |
|
||||
| france | `✔` | `✔` | `✔` | |
|
||||
| germany | `✔` | `✔` | `✔` | |
|
||||
| great_britain | `✔` | `✔` | `✔` | |
|
||||
| ireland | `✔` | `✔` | `✔` | |
|
||||
| italy | `✔` | `✔` | `✔` | |
|
||||
| luxembourg | `✔` | `✔` | `✔` | |
|
||||
| netherlands | `✔` | `✔` | `✔` | |
|
||||
| norway | `✔` | `✔` | `✔` | |
|
||||
| poland | `✔` | `✔` | `✔` | |
|
||||
| portugal | `✔` | | | RTX 3080 only |
|
||||
| spain | `✔` | `✔` | `✔` | |
|
||||
| sweden | `✔` | `✔` | `✔` | |
|
||||
| usa | `✔` | `✔` | `✔` | |
|
||||
|
||||
</details>
|
||||
|
||||
### Notifications
|
||||
|
||||
> :point_right: You can test your notification configuration by running `npm run test:notification`.
|
||||
|
||||
<details>
|
||||
<summary>Desktop</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `DESKTOP_NOTIFICATIONS` | Display desktop notifications using [node-notifier](https://www.npmjs.com/package/node-notifier) | Default: `false` |
|
||||
| `PLAY_SOUND` | Play this sound notification if a card is found | Relative path accepted, valid formats: wav, mp3, flac, E.g.: `path/to/notification.wav`, [free sounds available](https://notificationsounds.com/) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Discord</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `DISCORD_NOTIFY_GROUP` | Discord group you would like to notify | Can be comma separated, use role ID, E.g.: `<@&2834729847239842>` |
|
||||
| `DISCORD_WEB_HOOK` | Discord Web Hook URL | Can be comma separated, use whole webhook URL |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Email and SMS</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `EMAIL_PASSWORD` | Gmail password | See below if you have MFA |
|
||||
| `EMAIL_TO` | Destination Email | Defaults to username if not set. Can be comma separated |
|
||||
| `EMAIL_USERNAME` | Gmail address | E.g.: `jensen.robbed.us@gmail.com` |
|
||||
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS | E.g.: `att` or `att,verizon,google`, email configuration required. If multiple phone numbers are listed, enter a carrier for each phone number |
|
||||
| `PHONE_NUMBER` | 10 digit phone number(s) | E.g.: `1234567890` or `1234567890,0987654321,11112223333`, email configuration required |
|
||||
| `SMTP_ADDRESS` | IP Address or fqdn of smtp server |
|
||||
| `SMTP_PORT` | TCP Port number on which the smtp server is listening for connections | Default: `25` |
|
||||
|
||||
> :point_right: If you have multi-factor authentication (MFA), you will need to create an [app password](https://myaccount.google.com/apppasswords) and use this instead of your Gmail password.
|
||||
|
||||
#### Supported carriers
|
||||
|
||||
| Carrier | Environment variable | Notes |
|
||||
|:---:|:---:|:---:|
|
||||
| AT&T | `att` | |
|
||||
| AT&T Prepaid | `attgo` | |
|
||||
| Bell | `bell` | |
|
||||
| Fido | `fido` | |
|
||||
| Google | `google`| |
|
||||
| Koodo | `koodo` | |
|
||||
| Mint | `mint`| |
|
||||
| Rogers | `rogers` | |
|
||||
| Sprint | `sprint`| |
|
||||
| Telus | `telus`| |
|
||||
| T-Mobile | `tmobile`| |
|
||||
| Verizon | `verizon`| Works with Visible |
|
||||
| Virgin | `virgin`| |
|
||||
| Virgin (CA) | `virgin-ca`| |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>MQTT</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `MQTT_BROKER_ADDRESS` | IP address or URL of MQTT Broker | e.g 192.168.1.xxx or broker.hivemq.com |
|
||||
| `MQTT_BROKER_PORT` | Network port of MQTT Broker | Default: 1883 |
|
||||
| `MQTT_CLIENT_ID` | Unique ClientID (only if required by MQTT Broker), typically not required when only publishing alerts | eg. client-123456 |
|
||||
| `MQTT_PASSWORD` | MQTT password - only use with MQTT brokers on private networks, if required. Will not be sent over public networks for safety. | e.g mysecret |
|
||||
| `MQTT_QOS` | QoS level for published alerts to broker (https://www.npmjs.com/package/mqtt#about-qos) | Default: 0, Can be 0, 1, or 2 |
|
||||
| `MQTT_TOPIC` | Topic to publish alerts to. Can include %store%, %series%, %brand%, %model% for dynamic topics | Default: streetmerchant/alert e.g nv-alert/%store%/%series%/%brand%/%model%/alert |
|
||||
| `MQTT_USERNAME` | MQTT username - (only if required by MQTT Broker) | e.g myusername |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>PagerDuty</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `PAGERDUTY_INTEGRATION_KEY` | PagerDuty Events API v2 Integration Key. Obtain one in PagerDuty - <Service you want to use> - Integrations | |
|
||||
| `PAGERDUTY_SEVERITY` | Severity of PagerDuty events | Default: `info` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Philips Hue</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `PHILIPS_HUE_API_KEY` | Hue Bridge API Key | **Required**, generate key using instructions [here](https://developers.meethue.com/develop/get-started-2/). This will be used for both LAN and cloud access over the official Remote Hue API. |
|
||||
| `PHILIPS_HUE_LAN_BRIDGE_IP` | LAN IP Address of your Hue Bridge | LAN only, e.g. `192.168.x.x`|
|
||||
| `PHILIPS_HUE_LIGHT_IDS` | Light IDs | Optional (all if not supplied). Comma seperated, e.g.: `1`, `2` |See Hue App → About for IDs |
|
||||
| `PHILIPS_HUE_LIGHT_COLOR` | Color in RGB Format | Optional (NVIDIA green if not supplied). Comma separated, e.g.: `255`, `255`, `255`|
|
||||
| `PHILIPS_HUE_LIGHT_PATTERN` | `blink` or empty | Optional - lights will flash for 30 seconds if `blink` is supplied. |
|
||||
| `PHILIPS_HUE_CLOUD_ACCESS_TOKEN` | Remote Access Token | Cloud only, the access token obtained from Philips's Remote Hue API. Instructions to generate [here](https://developers.meethue.com/develop/hue-api/remote-authentication/). |
|
||||
| `PHILIPS_HUE_CLOUD_REFRESH_TOKEN` | Remote Refresh Token | Cloud only, the refresh token obtained from Philips's Remote Hue API. |
|
||||
| `PHILIPS_HUE_CLOUD_CLIENT_ID` | Remote Client ID | Cloud only, the client ID to use when accessing the Remote Hue API. |
|
||||
| `PHILIPS_HUE_CLOUD_CLIENT_SECRET` | Remote Client Secret | Cloud only, the client secret to use when accessing the Remote Hue API. |
|
||||
|
||||
> :point_right: [Video demonstration](https://vimeo.com/476083242)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Pushbullet</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `PUSHBULLET` | PushBullet API key | Generate at https://www.pushbullet.com/#settings/account | |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Pushover</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `PUSHOVER_TOKEN` | Pushover access token | Generate at https://pushover.net/apps/build | |
|
||||
| `PUSHOVER_USER` | Pushover username | |
|
||||
| `PUSHOVER_PRIORITY` | Pushover message priority |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Slack</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `SLACK_CHANNEL` | Slack channel for posting | E.g.: `update`, no need for `#` |
|
||||
| `SLACK_TOKEN` | Slack API token | |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Telegram</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token | |
|
||||
| `TELEGRAM_CHAT_ID` | Telegram chat ID | Comma seperated, e.g.: `123456789`, `123456789,987654321` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Twilio</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `TWILIO_ACCOUNT_SID` | Twilio Account SID | Can be found on twilio.com/console |
|
||||
| `TWILIO_AUTH_TOKEN` | Twilio Auth Token | Can be found on twilio.com/console |
|
||||
| `TWILIO_FROM_NUMBER` | Twilio provided phone number to send messages from | Include country code e.g +4401234567890 |
|
||||
| `TWILIO_TO_NUMBER` | Mobile number to send SMS to | Include country code e.g +4401234567890 |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Twitter</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `TWITTER_ACCESS_TOKEN_KEY` | Twitter Token Key | |
|
||||
| `TWITTER_ACCESS_TOKEN_SECRET` | Twitter Token Secret | |
|
||||
| `TWITTER_CONSUMER_KEY` | Twitter Consumer Key | Generate all Twitter keys at: https://developer.twitter.com/ |
|
||||
| `TWITTER_CONSUMER_SECRET` | Twitter Consumer Secret | |
|
||||
| `TWITTER_TWEET_TAGS` | Optional list of hashtags to append to the tweet message | E.g.: `#nvidia #nvidiastock` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Twitch</summary>
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `TWITCH_CLIENT_ID` | Twitch client ID | |
|
||||
| `TWITCH_CLIENT_SECRET`| Twitch client secret | |
|
||||
| `TWITCH_ACCESS_TOKEN` | Twitch access token | |
|
||||
| `TWITCH_REFRESH_TOKEN` | Twitch refresh token | |
|
||||
| `TWITCH_CHANNEL` | Twitch channel | |
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
## 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: Will this harm my computer?** No.
|
||||
|
||||
**Q: Have you gotten a card yet?** YES! :tada: :rocket:
|
||||
|
||||
<details>
|
||||
<summary>Screenshot</summary>
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
**Q: Will I get banned from of the stores?** Perhaps, but it's the risk we're willing to take! To help minimize this, take a look at [#1050](https://github.com/jef/streetmerchant/issues/1050).
|
||||
|
||||
**Q: I got a problem and need help!** File an [issue](https://github.com/jef/streetmerchant/issues/new/choose). I'll do my best to get to you. I work a full time job and this is only a hobby of mine.
|
||||
|
||||
**Q: How do I get the latest code?** Take look at this [wiki page](https://github.com/jef/streetmerchant/wiki/Troubleshoot:-General:-Getting-the-latest-code).
|
||||
|
||||
**Q: Why don't my notifications work?** There is probably an [issue](https://github.com/jef/streetmerchant/issues?q=is%3Aissue+sort%3Aupdated-desc+sound+is%3Aclosed) that has [already](https://github.com/jef/streetmerchant/issues/182) [been](https://github.com/jef/streetmerchant/issues/116) [resolved](https://github.com/jef/streetmerchant/issues/155).
|
||||
|
||||
**Q: I'd love to contribute, how do I do that?** Make a [pull request](https://github.com/jef/streetmerchant/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc)! All contributions are welcome.
|
||||
|
||||
**Q: How do I add a store?** Take a look at [this page](https://github.com/jef/streetmerchant/wiki/Help:-Configuration:-Adding-a-store) on the wiki.
|
||||
|
||||
**Q: Why do I have to download all this stuff just to get this bot working?** Well, I would rather you didn't either. See [#11](https://github.com/jef/streetmerchant/issues/11).
|
||||
|
||||
**Q: Why does Amazon show an error page (with a picture of an animal) instead of adding to cart or going to the detail page?** This is intended; see [#733](https://github.com/jef/streetmerchant/issues/733). This indicates that the item is out of stock and only available from a third-party seller (often at a markup).
|
||||
|
||||
**Q: I'm using streetmerchant in the cloud and X isn't working.** There is _a lot_ of undefined behavior with using streetmerchant in the cloud. Some sites may block IPs from your cloud provider. It is possible that a VPN will help circumvent these problems.
|
||||
|
||||
<p align="center"><a href="https://github.com/jef/streetmerchant#readme"><img src="https://raw.githubusercontent.com/jef/streetmerchant/main/media/terminal.gif" /></a></p>
|
||||
For more customization and information, visit [jef.codes/streetmerchant/getting-started](https://jef.codes/streetmerchant/getting-started).
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# About
|
||||
|
||||
## Background
|
||||
|
||||
Remember on September 17th, 2020 at 9 AM EST the Nvidia site went from **Notify Me** to **Out of Stock** instantly? Well, they didn't sell any cards. The real reason was that they weren't ready to sell them to us yet. That's right, they turned off their third party storefronts because they were being overloaded with our clicks. They still kept the other cards that use those APIs online, but they removed that one. It was re-enabled at some point for a brief moment, but the same thing happened -- servers overloaded with API requests.
|
||||
|
||||
This is where streetmerchant comes in. It doesn't buy anything for you, but it makes it more of a stress free job to refresh and check sites while you go about your daily business. People took off work, missed appointments, and gave up other lively needs in hopes to buy a _graphics card_. Now we reach beyond graphics cards in hopes for other products!
|
||||
|
||||
Please enjoy,
|
||||
|
||||
jef
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 865 KiB After Width: | Height: | Size: 865 KiB |
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CHANGELOG.md
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# FAQ
|
||||
|
||||
## 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.
|
||||
|
||||
## Will this harm my computer?
|
||||
|
||||
No.
|
||||
|
||||
## Have you gotten a card yet?
|
||||
|
||||
[Sure did!](https://i.imgur.com/59CRzGq.png)
|
||||
|
||||
## Will I get banned from of the stores?
|
||||
|
||||
Perhaps, but it's the risk we're willing to take! To help minimize this, take a look at [#1050](https://github.com/jef/streetmerchant/issues/1050).
|
||||
|
||||
## I got a problem and need help
|
||||
|
||||
File an [issue](https://github.com/jef/streetmerchant/issues/new/choose). I'll do my best to get to you. I work a full time job and this is only a hobby of mine.
|
||||
|
||||
## How do I get the latest code?
|
||||
|
||||
Run the following commands:
|
||||
|
||||
```shell
|
||||
git pull origin main
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
If you changed the code at all, this will most likely fail. You can clear out your changes by doing:
|
||||
|
||||
```shell
|
||||
git checkout .
|
||||
git pull origin main
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
You can also to [git-stash](https://git-scm.com/docs/git-stash), but we won't expand on that here.
|
||||
|
||||
|
||||
## Why don't my notifications work?
|
||||
|
||||
There is probably an [issue](https://github.com/jef/streetmerchant/issues?q=is%3Aissue+sort%3Aupdated-desc+sound+is%3Aclosed) that has [already](https://github.com/jef/streetmerchant/issues/182) [been](https://github.com/jef/streetmerchant/issues/116) [resolved](https://github.com/jef/streetmerchant/issues/155).
|
||||
|
||||
## I'd love to contribute, how do I do that?
|
||||
|
||||
Make a [pull request](https://github.com/jef/streetmerchant/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc)! All contributions are welcome.
|
||||
|
||||
## How do I add a store?
|
||||
|
||||
Take a look at [this page](https://github.com/jef/streetmerchant/wiki/Help:-Configuration:-Adding-a-store) on the wiki.
|
||||
|
||||
## Why do I have to download all this stuff just to get this bot working?
|
||||
|
||||
Well, I would rather you didn't either. See [#11](https://github.com/jef/streetmerchant/issues/11).
|
||||
|
||||
## Why does Amazon show an error page (with a picture of an animal) instead of adding to cart or going to the detail page?
|
||||
|
||||
This is intended; see [#733](https://github.com/jef/streetmerchant/issues/733). This indicates that the item is out of stock and only available from a third-party seller (often at a markup).
|
||||
|
||||
## I'm using streetmerchant in the cloud and X isn't working.
|
||||
|
||||
There is _a lot_ of undefined behavior with using streetmerchant in the cloud. Some sites may block IPs from your cloud provider. It is possible that a VPN will help circumvent these problems.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Getting started
|
||||
|
||||
You do not need any computer skills, smarts, or anything of that nature. You are very capable as you have made it this far. Some basic understanding how a terminal, git, and or Node.js is a bonus, but that does not limit you to getting streetmerchant running!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [git](https://git-scm.com/)
|
||||
- Either [Node.js 14](https://nodejs.org/en/) or [Docker](https://docs.docker.com/get-docker/) (advanced users)
|
||||
|
||||
## Using Node.js
|
||||
|
||||
| Reference | Note |
|
||||
|:---:|---|
|
||||
| tag | Example, `v1.0.0`; stable |
|
||||
| `main` | Latest HEAD; not tagged, could be unstable |
|
||||
|
||||
- [Node.js 14](https://nodejs.org/en/)
|
||||
- Clone this project `git clone https://github.com/jef/streetmerchant.git`.
|
||||
- To checkout a particular reference, use `git checkout <ref name>` after cloning.
|
||||
- Navigate to this project by entering `cd streetmerchant`.
|
||||
- Run `npm install`.
|
||||
- Make a copy of `.env-example` and name it `.env`.
|
||||
- Edit the `.env` file to your liking using a text editor (like [vscode](https://code.visualstudio.com/)).
|
||||
- Run `npm run start` to start.
|
||||
|
||||
At any point you want the program to stop, use ++ctrl+c++.
|
||||
|
||||
???+ tip
|
||||
Community based help can also be found on the [wiki](https://github.com/jef/streetmerchant/wiki). Feel free to check that out if you're having problems running. If you're still having problems running, you're probably not the first. Make some searches through the [GitHub issues](https://github.com/jef/streetmerchant/issues) before making one.
|
||||
|
||||
## Using Docker
|
||||
|
||||
Available via GitHub Container Registry.
|
||||
|
||||
| Tag | Note |
|
||||
|:---:|---|
|
||||
| `latest` | Latest release; stable |
|
||||
| `nightly` | Latest HEAD each day at midnight UTC; could be unstable |
|
||||
|
||||
```sh
|
||||
# to run docker nightly
|
||||
docker run --cap-add=SYS_ADMIN \
|
||||
-it --rm --env-file ./.env \
|
||||
ghcr.io/jef/streetmerchant:nightly
|
||||
|
||||
# to test notifications
|
||||
docker run --cap-add=SYS_ADMIN \
|
||||
-it --rm --env-file ./.env \
|
||||
ghcr.io/jef/streetmerchant:nightly test:notification:production
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
To customize streetmerchant, make a copy of `.env-example` as `.env` and make any changes to your liking. View [Reference](docs/reference/application.md) for more information on variables and their usage.
|
||||
|
||||
???+ tip
|
||||
All environment variables are optional.
|
||||
|
||||
## For developers
|
||||
|
||||
The command `npm run start:dev` can be used instead of `npm run start` to automatically restart the project when filesystem changes are detected in the `src/` folder or `.env` file.
|
||||
@@ -0,0 +1,111 @@
|
||||
# General
|
||||
|
||||
## Adding a store
|
||||
|
||||
???+ note
|
||||
This is subject to change in the future
|
||||
|
||||
In the following examples, I will be using "NewStore" as the store I'm wanting to add.
|
||||
|
||||
### Creating a store file
|
||||
|
||||
First, create a TypeScript file in `src/store/model`. In this example, I'll create a file named `new-store.ts`. At this point, you can copy and paste any of the other stores and change accordingly.
|
||||
|
||||
#### How to grab a container (aka selector)
|
||||
|
||||
For the containers, what you'll wanna do is use <kbd>F12</kbd> on the site you want to Inspect and click this button
|
||||
|
||||

|
||||
|
||||
Hover over the item you want and it should give you the context:
|
||||
|
||||

|
||||
|
||||
You can also right-click on any website element and select 'Inspect'. That should also give you the same results.
|
||||
|
||||
Some people will decide to choose a parent element as it can be unique. Like this case!
|
||||
|
||||
I'd rather use `.button.spin-button.prod-ProductCTA--primary.button--primary` instead of `.spin-button-children` as there are probably other elements on the page that are also `.spin-button-children`.
|
||||
|
||||
The reason why we use these selectors anyway is to wait for the webpage to load these specific elements, to help eliminate false positives.
|
||||
|
||||
For easily getting the selector, you can also copy it by right clicking on the tag, Copy > Copy selector.
|
||||
|
||||

|
||||
|
||||
### Updating the models
|
||||
|
||||
You'll now want to add the new store to `src/store/model/index.ts`.
|
||||
|
||||
This is what it will look like:
|
||||
|
||||
```diff
|
||||
--- a/src/store/model/index.ts
|
||||
+++ b/src/store/model/index.ts
|
||||
@@ -48,6 +48,7 @@ import {MicroCenter} from './microcenter';
|
||||
import {Mindfactory} from './mindfactory';
|
||||
import {Newegg} from './newegg';
|
||||
import {NeweggCa} from './newegg-ca';
|
||||
+import {NewStore} from './new-store';
|
||||
import {Notebooksbilliger} from './notebooksbilliger';
|
||||
import {Novatech} from './novatech';
|
||||
import {Nvidia} from './nvidia';
|
||||
@@ -123,6 +124,7 @@ export const storeList = new Map([
|
||||
[Mindfactory.name, Mindfactory],
|
||||
[Newegg.name, Newegg],
|
||||
[NeweggCa.name, NeweggCa],
|
||||
+ [NewStore.name, NewStore],
|
||||
[Notebooksbilliger.name, Notebooksbilliger],
|
||||
[Novatech.name, Novatech],
|
||||
[Nvidia.name, Nvidia],
|
||||
```
|
||||
|
||||
After that, you're pretty much set. If you plan on adding new models or series, you will have to add them to `src/store/model/store.ts`.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```diff
|
||||
--- a/src/store/model/store.ts
|
||||
+++ b/src/store/model/store.ts
|
||||
@@ -23,6 +23,7 @@ export type Brand =
|
||||
| 'kfa2'
|
||||
| 'microsoft'
|
||||
| 'msi'
|
||||
+ | 'new brand'
|
||||
| 'nvidia'
|
||||
| 'palit'
|
||||
| 'pny'
|
||||
@@ -37,6 +38,7 @@ export type Series =
|
||||
| '3070'
|
||||
| '3080'
|
||||
| '3090'
|
||||
+ | 'new series'
|
||||
| 'rx6800'
|
||||
| 'rx6800xt'
|
||||
| 'rx6900xt'
|
||||
@@ -83,6 +85,7 @@ export type Model =
|
||||
| 'ichill x2'
|
||||
| 'ichill x3'
|
||||
| 'ichill x4'
|
||||
+ | 'new model'
|
||||
| 'nitro+'
|
||||
| 'nitro oc se'
|
||||
| 'nitro oc'
|
||||
```
|
||||
|
||||
And voila! You're done! If you'd like to contribute to the project, feel free to create a [Pull Request](https://github.com/jef/streetmerchant/compare)! Don't forget to add the store (and brand, model, and series if you added) to the `README.md`.
|
||||
|
||||
???+ tip
|
||||
Here's an [example](https://github.com/jef/streetmerchant/commit/af96c5f2e808af7496f3c3299e4cf173105de48b).
|
||||
|
||||
## Creating a Discord webhook
|
||||
|
||||
Take a look at Discord's [Intro to Webhooks](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks), that should get you going.
|
||||
|
||||
This is the main portion:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Use the full URL that you just copied and set that value to `DISOCRD_WEB_HOOK`.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Troubleshoot
|
||||
|
||||
## Captcha issues
|
||||
|
||||
### Option 1
|
||||
|
||||
If you're running into _a lot_ of captcha problems, be sure to update your user agent by searching ["what's my user agent" on Google](https://www.google.com/search?q=whats+my+user+agent).
|
||||
|
||||

|
||||
|
||||
You can update your user agent by using `USER_AGENT="your-result"`.
|
||||
|
||||
### Option 2
|
||||
|
||||
If you're _still_ running into problems, try running in headful mode: `HEADLESS="false"`.
|
||||
|
||||
This will open a browser and run streetmerchant. Note that this isn't a great solution for those running in a headless environment, i.e.: VPS, cloud, docker. Instead, it would be a good solution for those running on separate computer that won't be blocked by running in the background.
|
||||
|
||||
### Option 3
|
||||
|
||||
As a last case scenario, use `PUPPETEER_EXECUTABLE_PATH`. This will use your computer's Chrome browser. You can run this is headless or headful mode.
|
||||
|
||||
> From the puppeteer doc:
|
||||
>
|
||||
> `PUPPETEER_EXECUTABLE_PATH` - specify an executable path to be used in `puppeteer.launch`. See [puppeteer.launch([options])](https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#puppeteerlaunchoptions) on how the executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
|
||||
|
||||
For example:
|
||||
|
||||
`.env`:
|
||||
|
||||
```
|
||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
|
||||
```
|
||||
|
||||
This will vary depending on your operating system and install path. Please use full paths.
|
||||
|
||||
## macOS code signing
|
||||
|
||||
If you're getting a popup like this:
|
||||
|
||||

|
||||
|
||||
Then run this command:
|
||||
|
||||
```sh
|
||||
sudo codesign --force --deep --sign - ./node_modules/puppeteer/.local-chromium/mac-800071/chrome-mac/Chromium.app
|
||||
```
|
||||
|
||||
???+ tip
|
||||
The `mac-800071` may be different on your machine, so I would start from `./node_modules/puppeteer/.local-chromium` and auto complete from there.
|
||||
@@ -0,0 +1,37 @@
|
||||
[](https://jef.codes/streetmerchant)
|
||||
|
||||
## Features
|
||||
|
||||
First and foremost, this service _will not_ automatically buy for you.
|
||||
|
||||
- **Checks stock continuously** -- runs 24/7, 365, looking for the items you want.
|
||||
- **Ready for checkout** -- ability to add to cart when available and even opens the browser for you.
|
||||
- **Notifications galore** -- when you're not by your computer, worry free with notifications to most platforms and devices when an item comes in stock.
|
||||
|
||||
## Getting started
|
||||
|
||||
You'll find most of the content on the left sidebar. The right sidebar will help you navigate a page.
|
||||
|
||||
### Contributing
|
||||
|
||||
- Give helpful tips and tricks to the [community based wiki](https://github.com/jef/streetmerchant/wiki).
|
||||
- Add to the documentation through [pull requests](https://github.com/jef/streetmerchant/pulls).
|
||||
- Fork and make a pull request to the repository.
|
||||
|
||||
### Looking for help
|
||||
|
||||
- File a [GitHub issue](https://github.com/jef/streetmerchant/issues/new/choose).
|
||||
- Join us on [Discord](https://discord.gg/gbVY4vB9JF).
|
||||
|
||||
### Supporting the project
|
||||
|
||||
The best way to support me is to donate to [Diabetes Research Institute](https://www.diabetesresearch.org/Give).
|
||||
|
||||
> The Diabetes Research Institute leads the world in cure-focused diabetes research.
|
||||
>
|
||||
> [diabetesresearch.org](https://www.diabetesresearch.org/about-DRI)
|
||||
|
||||
If you feel inclined to support me directly, here are those options:
|
||||
|
||||
- [GitHub Sponsors](https://github.com/sponsors/jef)
|
||||
- [Paypal](https://www.paypal.me/jxf)
|
||||
@@ -0,0 +1,29 @@
|
||||
# Application
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `AUTO_ADD_TO_CART` | Enable auto add to cart on support stores, default: `true` |
|
||||
| `BROWSER_TRUSTED` | Skip Chromium Sandbox. Useful for containerized environments, default: `false` |
|
||||
| `HEADLESS` | Puppeteer to run headless or not. Debugging related, default: `true` |
|
||||
| `INCOGNITO` | Puppeteer to run incognito or not. Debugging related, default: `false` |
|
||||
| `IN_STOCK_WAIT_TIME` | Time to wait between requests to the same link if it has that card in stock. In seconds, default: `0` |
|
||||
| `LOG_LEVEL` | [Logging levels](https://github.com/winstonjs/winston#logging-levels). Debugging related, default: `info` |
|
||||
| `LOW_BANDWIDTH` | Blocks images/fonts to reduce traffic. Disables ad blocker, default: `false` |
|
||||
| `OPEN_BROWSER` | Toggle for whether or not the browser should open when item is found. Default: `true` |
|
||||
| `PAGE_BACKOFF_MIN` | Minimum backoff time between retrying requests for the same store when a forbidden response is received. Default: `10000` |
|
||||
| `PAGE_BACKOFF_MAX` | Maximum backoff time between retrying requests for the same store when a forbidden response is received. Default: `3600000` |
|
||||
| `PAGE_SLEEP_MIN` | Minimum sleep time between queries of the same product page. In milliseconds, default: `5000` |
|
||||
| `PAGE_SLEEP_MAX` | Maximum sleep time between queries of the same product page. In milliseconds, default: `10000` |
|
||||
| `PAGE_TIMEOUT` | Navigation Timeout in milliseconds. `0` for infinite, default: `30000` |
|
||||
| `PROXY_PROTOCOL` | protocol of proxy server, such as `socks5`. Default: `http` |
|
||||
| `PROXY_ADDRESS` | IP Address or fqdn of proxy server |
|
||||
| `PROXY_PORT` | TCP Port number on which the proxy is listening for connections. Default: `80` |
|
||||
| `SCREENSHOT` | Capture screenshot of page if a card is found. Default: `true` |
|
||||
| `USER_AGENT` | Custom User-Agents headers for HTTP requests. Newline separated, e.g.: `USER_AGENT_STRING1 \n USER_AGENT_STRING2`. Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` |
|
||||
| `WEB_PORT` | Starts a webserver to be able to control the bot while it is running. Setting this value starts this service. Default: `` |
|
||||
|
||||
???+ note
|
||||
You can find your computer's user agent by [searching google for "my user agent"](http://google.com/search?q=my+user+agent)
|
||||
|
||||
???+ tip
|
||||
Data usage is [known to be high](https://github.com/jef/streetmerchant/issues?q=is%3Aissue+sort%3Aupdated-desc+bandwidth). This is expected as the program scrapes many websites in parallel 24/7. To help reduce this, use `LOW_BANDWIDTH="true"`. We are looking into other solutions as well, but is low priority.
|
||||
@@ -0,0 +1,203 @@
|
||||
# Filter
|
||||
|
||||
| Environment variable | Description | Notes |
|
||||
|:---:|---|---|
|
||||
| `COUNTRY` | [Supported country](#supported-countries) you want to be scraped | Only used with `nvidia-api`, default: `usa` |
|
||||
| `MAX_PRICE_SERIES_3060TI` | Maximum price allowed for a match, applies 3060 Ti series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_3070` | Maximum price allowed for a match, applies 3070 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_3080` | Maximum price allowed for a match, applies 3080 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_3090` | Maximum price allowed for a match, applies 3090 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_CORSAIR_SF` | Maximum price allowed for a match, applies to Corsair PSUs | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PSUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5600` | Maximum price allowed for a match, applies AMD 5600 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5800` | Maximum price allowed for a match, applies AMD 5800 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5900` | Maximum price allowed for a match, applies AMD 5900 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_RYZEN5950` | Maximum price allowed for a match, applies AMD 5950 series cpus | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - CPUs above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_SONYPS5C` | Maximum price allowed for a match, applies PS5 console | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PS5 above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_SONYPS5DE` | Maximum price allowed for a match, applies PS5 digital edition | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PS5 above `1234` will be skipped. |
|
||||
| `MAX_PRICE_SERIES_TEST` | Maximum price allowed for a match, applies `test:series` | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - PS5 above `1234` will be skipped. |
|
||||
| `MICROCENTER_LOCATION` | Specific MicroCenter location(s) to search | Comma separated, e.g.: `marietta,duluth`, default: `web` |
|
||||
| `NVIDIA_ADD_TO_CART_ATTEMPTS` | The maximum number of times the `nvidia-api` add to cart feature will be attempted before failing | Default: `10` |
|
||||
| `NVIDIA_SESSION_TTL` | The time in milliseconds to keep the cart active while using `nvidia-api` | Default: `60000` |
|
||||
| `SHOW_ONLY_BRANDS` | Filter to show specified brands | Comma separated, e.g.: `evga,zotac` |
|
||||
| `SHOW_ONLY_MODELS` | Filter to show specified models | Both supported formats are comma separated <br/><br/>1. Standard E.g.: `founders edition,rog strix` <br/><br/> 2. Advanced E.g: `MODEL:SERIES`, E.g: `founders edition:3090,rog strix` |
|
||||
| `SHOW_ONLY_SERIES` | Filter to show specified series | Comma separated, e.g.: `3080,ryzen5900` |
|
||||
| `STORES` | [Supported stores](#supported-stores) you want to be scraped | Both supported formats are comma separated <br/><br/>1. Standard E.g.: `"nvidia"` <br/><br/> 2. Advanced E.g: `STORE:PAGE_SLEEP_MIN:PAGE_SLEEP_MAX`, E.g: `nvidia:10000:30000` <br/><br/>Default: `nvidia` |
|
||||
|
||||
## Supported stores
|
||||
|
||||
Used with the `STORES` variable.
|
||||
|
||||
| Stores | Environment variable |
|
||||
|:---:|:---:|
|
||||
| Adorama | `adorama`|
|
||||
| Alternate (DE) | `alternate`|
|
||||
| Alternate (NL) | `alternate-nl`|
|
||||
| Amazon | `amazon`|
|
||||
| Amazon (CA) | `amazon-ca`|
|
||||
| Amazon (DE) | `amazon-de`|
|
||||
| Amazon (DE) Warehouse | `amazon-de-warehouse`|
|
||||
| Amazon (ES) | `amazon-es`|
|
||||
| Amazon (FR) | `amazon-fr`|
|
||||
| Amazon (IT) | `amazon-it`|
|
||||
| Amazon (NL) | `amazon-nl`|
|
||||
| Amazon (UK) | `amazon-uk`|
|
||||
| AMD | `amd`|
|
||||
| AMD (CA) | `amd-ca`|
|
||||
| AMD (DE) | `amd-de`|
|
||||
| AMD (IT) | `amd-it`|
|
||||
| AntOnline | `antonline`|
|
||||
| Argos (UK) | `argos`|
|
||||
| Aria PC (UK) | `aria`|
|
||||
| ARLT (DE) | `arlt`|
|
||||
| ASUS | `asus` |
|
||||
| ASUS (DE) | `asus-de` |
|
||||
| Azerty (NL) | `azerty`|
|
||||
| B&H | `bandh`|
|
||||
| Best Buy | `bestbuy`|
|
||||
| Best Buy (CA) | `bestbuy-ca`|
|
||||
| Box (UK) | `box`|
|
||||
| CanadaComputers (CA) | `canadacomputers` |
|
||||
| Caseking (DE) | `caseking`|
|
||||
| CCL (UK) | `ccl`|
|
||||
| Comet (IT) | `comet`|
|
||||
| Computeruniverse (DE) | `computeruniverse` |
|
||||
| Coolblue (NL) | `coolblue`|
|
||||
| Coolmod (ES) | `coolmod`|
|
||||
| Corsair | `corsair`|
|
||||
| Currys (UK) | `currys`|
|
||||
| Cyberport (DE) | `cyberport` |
|
||||
| eBuyer (UK) | `ebuyer`|
|
||||
| El Corte Inglés | `elcorteingles`|
|
||||
| ePrice (IT) | `eprice`|
|
||||
| Euronics (IT) | `euronics`|
|
||||
| Euronics (DE) | `euronics-de`|
|
||||
| EVGA | `evga`|
|
||||
| EVGA (EU) | `evga-eu`|
|
||||
| Expert | `expert`|
|
||||
| Galaxus (DE) | `galaxus`|
|
||||
| Game (UK) | `game`|
|
||||
| Gamestop | `gamestop`|
|
||||
| Gamestop (DE) | `gamestop-de`|
|
||||
| Kabum (BR) | `kabum`|
|
||||
| Mediamarkt (DE) | `mediamarkt`|
|
||||
| Medimax | `medimax`|
|
||||
| MemoryExpress (CA) | `memoryexpress`|
|
||||
| Micro Center | `microcenter`|
|
||||
| Mindfactory (DE) | `mindfactory` |
|
||||
| Newegg | `newegg`|
|
||||
| Newegg (CA) | `newegg-ca`|
|
||||
| Notebooksbilliger (DE) |`notebooksbilliger`|
|
||||
| Novatech (UK) | `novatech`|
|
||||
| Nvidia | `nvidia`|
|
||||
| Nvidia (API) | `nvidia-api`|
|
||||
| Office Depot | `officedepot`|
|
||||
| Otto | `otto`|
|
||||
| Overclockers (UK) | `overclockers`|
|
||||
| PCComponentes (ES) | `pccomponentes`|
|
||||
| PlayStation | `playstation`|
|
||||
| PNY | `pny`|
|
||||
| Proshop (DE) | `proshop-de`|
|
||||
| Proshop (DK) | `proshop-dk`|
|
||||
| Saturn (DE) | `saturn`|
|
||||
| Scan (UK) | `scan`|
|
||||
| Smyths Toys (UK) | `smythstoys`|
|
||||
| Spielegrotte | `spielegrotte`|
|
||||
| Target | `target`|
|
||||
| Unieuro (IT) | `unieuro`|
|
||||
| Very (UK) | `very`|
|
||||
| Walmart | `walmart`|
|
||||
| Wipoid | `wipoid`|
|
||||
| Zotac | `zotac`|
|
||||
| TopAchat | `topachat`|
|
||||
|
||||
### Micro Center stores
|
||||
|
||||
Used with the `MICROCENTER_LOCATION` variable.
|
||||
|
||||
???+ note
|
||||
Before using `web`, please review [this issue comment](https://github.com/jef/streetmerchant/issues/442#issuecomment-703297393).
|
||||
|
||||
| | | | |
|
||||
|---|---|---|---|
|
||||
| `brooklyn` | `brentwood` | `cambridge` | `chicago` |
|
||||
| `columbus` | `dallas` | `denver` | `duluth` |
|
||||
| `fairfax` | `flushing` | `houston` | `madison-heights` |
|
||||
| `marietta` | `mayfield-heights` | `north-jersey` | `overland-park` |
|
||||
| `parkville` | `rockville` | `sharonville` | `st-davids` |
|
||||
| `st-louis-park` | `tustin` | `westbury` | `westmont` |
|
||||
| `yonkers` |
|
||||
|
||||
## Supported brands and models
|
||||
|
||||
Used with the `SHOW_ONLY_BRANDS` and `SHOW_ONLY_MODELS` variables.
|
||||
|
||||
| Brand | Model |
|
||||
|:---:|---|
|
||||
| `amd` | `5600x`, `5800x`, `5900x`, `5950x`, `amd reference` |
|
||||
| `asus` | `dual`, `dual oc`, `strix`, `strix oc`, `tuf`, `tuf oc` |
|
||||
| `corsair` | `750 platinum`, `600 platinum` |
|
||||
| `evga` | `ftw3`, `ftw3 ultra`, `xc3`, `xc3 black`, `xc3 ultra` |
|
||||
| `gainward` | `phantom gs`, `phoenix`, `phoenix gs`, `phoenix gs oc` |
|
||||
| `gigabyte` | `aorus master`, `aorus xtreme`, `eagle`, `eagle oc`, `gaming`, `gaming oc`, `turbo`, `vision`, `vision oc` |
|
||||
| `inno3d` | `gaming x3`, `ichill x3`, `ichill x4`, `twin x2 oc` |
|
||||
| `kfa2` | `sg`, `sg oc` |
|
||||
| `microsoft` | `xbox series x`, `xbox series s` |
|
||||
| `msi` | `gaming x trio`, `ventus 2x oc`, `ventus 3x`, `ventus 3x oc` |
|
||||
| `nvidia` | `founders edition` |
|
||||
| `palit` | `gamerock oc`, `gaming pro`, `gaming pro oc` |
|
||||
| `pny` | `dual fan`, `xlr8 revel`, `xlr8 uprising` |
|
||||
| `sony` | `ps5 console`, `ps5 digital` |
|
||||
| `xfx` | `merc`, `amd reference` |
|
||||
| `zotac` | `amp holo`, `amp extreme holo`, `trinity`, `trinity oc`, `twin edge`, `twin edge oc` |
|
||||
|
||||
## Supported series
|
||||
|
||||
Used with the `SHOW_ONLY_SERIES` variable.
|
||||
|
||||
| Series | Environment variable |
|
||||
|:---:|:---:|
|
||||
| `AMD Ryzen 5600x` | `ryzen5600` |
|
||||
| `AMD Ryzen 5800x` | `ryzen5800` |
|
||||
| `AMD Ryzen 5900x` | `ryzen5900` |
|
||||
| `AMD Ryzen 5950x` | `ryzen5950` |
|
||||
| `AMD RX 6800` | `rx6800` |
|
||||
| `AMD RX 6800XT` | `rx6800xt` |
|
||||
| `AMD RX 6900XT` | `rx6900xt` |
|
||||
| `Nvidia RTX 3060 Ti` | `3060ti` |
|
||||
| `Nvidia RTX 3070` | `3070` |
|
||||
| `Nvidia RTX 3080` | `3080` |
|
||||
| `Nvidia RTX 3090` | `3090` |
|
||||
| `Corsair SFX PSU` | `sf` |
|
||||
| `Sony PS5` | `sonyps5c` |
|
||||
| `Sony PS5 Digital Edition` | `sonyps5de` |
|
||||
| `Xbox Series S` | `xboxss` |
|
||||
| `Xbox Series X` | `xboxsx` |
|
||||
|
||||
## Supported countries
|
||||
|
||||
Used with the `COUNTRY` variable.
|
||||
|
||||
???+ attention
|
||||
Used _only_ with `nvidia` and `nvidia-api`.
|
||||
|
||||
| Country | 3080 FE | 3090 FE | Test Card | Notes |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| austria | `✔` | `✔` | `✔` | |
|
||||
| belgium | `✔` | `✔` | `✔` | |
|
||||
| canada | `✔` | `✔` | `✔` | |
|
||||
| czechia | `✔` | `✔` | `✔` | |
|
||||
| denmark | `✔` | | `✔` | Missing RTX 3090 |
|
||||
| finland | `✔` | | `✔` | Missing RTX 3090 |
|
||||
| france | `✔` | `✔` | `✔` | |
|
||||
| germany | `✔` | `✔` | `✔` | |
|
||||
| great_britain | `✔` | `✔` | `✔` | |
|
||||
| ireland | `✔` | `✔` | `✔` | |
|
||||
| italy | `✔` | `✔` | `✔` | |
|
||||
| luxembourg | `✔` | `✔` | `✔` | |
|
||||
| netherlands | `✔` | `✔` | `✔` | |
|
||||
| norway | `✔` | `✔` | `✔` | |
|
||||
| poland | `✔` | `✔` | `✔` | |
|
||||
| portugal | `✔` | | | RTX 3080 only |
|
||||
| spain | `✔` | `✔` | `✔` | |
|
||||
| sweden | `✔` | `✔` | `✔` | |
|
||||
| usa | `✔` | `✔` | `✔` | |
|
||||
@@ -0,0 +1,170 @@
|
||||
# Notification
|
||||
|
||||
You can test your notification configuration by running `npm run test:notification`.
|
||||
|
||||
## Desktop
|
||||
|
||||
| Environment variable | Description |
|
||||
|---|---|
|
||||
| `DESKTOP_NOTIFICATIONS` | Display desktop notifications using [node-notifier](https://www.npmjs.com/package/node-notifier). |
|
||||
| `PLAY_SOUND` | Play this sound notification if a product is found. Relative path accepted, valid formats: wav, mp3, flac, E.g.: `path/to/notification.wav`, [free sounds available](https://notificationsounds.com/) |
|
||||
|
||||
???+ attention
|
||||
If you're on Windows, you must have the proper library to run.
|
||||
|
||||
## Discord
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `DISCORD_NOTIFY_GROUP` | Discord group you would like to notify. Can be comma separated |
|
||||
| `DISCORD_WEB_HOOK` | Discord Web Hook URL. Can be comma separated. Use whole webhook URL |
|
||||
|
||||
???+ note
|
||||
- If you're using a role, please use `<@&2834729847239842>`
|
||||
- If you're using a user, please use `<@2834729847239842>`
|
||||
|
||||
## Email and SMS
|
||||
|
||||
Default provider is Gmail. If you use a different email provider, you must provide SMTP settings.
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `EMAIL_PASSWORD` | Email password. (See below for Gmail MFA users) |
|
||||
| `EMAIL_TO` | Destination Email. Defaults to username if not set. Can be comma separated |
|
||||
| `EMAIL_USERNAME` | Email address |
|
||||
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS. E.g.: `att` or `att,verizon,google`, email configuration required. If multiple phone numbers are listed, enter a carrier for each phone number |
|
||||
| `PHONE_NUMBER` | 10 digit phone number(s). E.g.: `1234567890` or `1234567890,0987654321,11112223333`, email configuration required |
|
||||
| `SMTP_ADDRESS` | IP Address or FQDN of SMTP server |
|
||||
| `SMTP_PORT` | TCP Port number on which the smtp server is listening for connections. Default: `25` |
|
||||
|
||||
???+ attention
|
||||
If you use Gmail and have multi-factor authentication (MFA), you will need to create an [app password](https://myaccount.google.com/apppasswords) and use this instead of your Gmail password.
|
||||
|
||||
### Supported carriers
|
||||
|
||||
| Carrier | Environment variable |
|
||||
|:---:|:---:|
|
||||
| AT&T | `att` |
|
||||
| AT&T Prepaid | `attgo` |
|
||||
| Bell | `bell` |
|
||||
| Fido | `fido` |
|
||||
| Google | `google`|
|
||||
| Koodo | `koodo` |
|
||||
| Mint | `mint`|
|
||||
| Rogers | `rogers` |
|
||||
| Sprint | `sprint`|
|
||||
| Telus | `telus`|
|
||||
| T-Mobile | `tmobile`|
|
||||
| Verizon | `verizon`|
|
||||
| Virgin | `virgin`|
|
||||
| Virgin (CA) | `virgin-ca`|
|
||||
| Visible | `visible`|
|
||||
|
||||
## MQTT
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `MQTT_BROKER_ADDRESS` | IP address or URL of MQTT Broker, e.g.: `192.168.1.xxx` or `broker.hivemq.com` |
|
||||
| `MQTT_BROKER_PORT` | Network port of MQTT Broker. Default: `1883` |
|
||||
| `MQTT_CLIENT_ID` | Unique Client ID (only if required by MQTT Broker), typically not required when only publishing alerts |
|
||||
| `MQTT_PASSWORD` | MQTT password - only use with MQTT brokers on private networks, if required. Will not be sent over public networks for safety |
|
||||
| `MQTT_QOS` | QoS level for published alerts to broker (https://www.npmjs.com/package/mqtt#about-qos). Default: `0`, Can be `0`, `1`, or `2` |
|
||||
| `MQTT_TOPIC` | Topic to publish alerts to. Can include `%store%`, `%series%`, `%brand%`, `%model%` for dynamic topics. Default: `streetmerchant/alert`. E.g.: `nv-alert/%store%/%series%/%brand%/%model%/alert` |
|
||||
| `MQTT_USERNAME` | MQTT username - (only if required by MQTT Broker) |
|
||||
|
||||
## PagerDuty
|
||||
|
||||
Obtained in PagerDuty - <Service you want to use> - Integrations
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `PAGERDUTY_INTEGRATION_KEY` | PagerDuty Events API v2 Integration Key. |
|
||||
| `PAGERDUTY_SEVERITY` | Severity of PagerDuty events |
|
||||
|
||||
## Philips Hue
|
||||
|
||||
Generate required keys using [instructions](https://developers.meethue.com/develop/get-started-2/). This will be used for both LAN and cloud access over the official Remote Hue API.
|
||||
|
||||
For cloud only usage, instructions to generate are located [here](https://developers.meethue.com/develop/hue-api/remote-authentication/).
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `PHILIPS_HUE_API_KEY` | Hue Bridge API Key |
|
||||
| `PHILIPS_HUE_LAN_BRIDGE_IP` | LAN IP Address of your Hue Bridge. LAN only, e.g. `192.168.x.x` |
|
||||
| `PHILIPS_HUE_LIGHT_IDS` | Light IDs. All lights if not supplied. Can be comma separated, e.g.: `1,2`. See Hue App -> About for IDs |
|
||||
| `PHILIPS_HUE_LIGHT_COLOR` | Color in RGB Format. Nvidia green if not supplied. Can be comma separated, e.g.: `255,255,255` |
|
||||
| `PHILIPS_HUE_LIGHT_PATTERN` | Lights will flash for 30 seconds if `blink` is given |
|
||||
| `PHILIPS_HUE_CLOUD_ACCESS_TOKEN` | Cloud Access Token. Cloud only |
|
||||
| `PHILIPS_HUE_CLOUD_REFRESH_TOKEN` | Cloud Refresh Token. Cloud only |
|
||||
| `PHILIPS_HUE_CLOUD_CLIENT_ID` | Cloud Client ID. Cloud only |
|
||||
| `PHILIPS_HUE_CLOUD_CLIENT_SECRET` | Cloud Client Secret. Cloud only |
|
||||
|
||||
> :point_right: Here's a [video demonstration](https://vimeo.com/476083242).
|
||||
|
||||
## Pushbullet
|
||||
|
||||
Generate token at https://www.pushbullet.com/#settings/account.
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `PUSHBULLET` | PushBullet API key |
|
||||
|
||||
## Pushover
|
||||
|
||||
Generate token at https://pushover.net/apps/build.
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `PUSHOVER_TOKEN` | Pushover access token |
|
||||
| `PUSHOVER_USER` | Pushover username |
|
||||
| `PUSHOVER_PRIORITY` | Pushover message priority |
|
||||
|
||||
## Slack
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `SLACK_CHANNEL` | Slack channel for posting |
|
||||
| `SLACK_TOKEN` | Slack API token |
|
||||
|
||||
## Telegram
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token |
|
||||
| `TELEGRAM_CHAT_ID` | Telegram chat ID. Can be comma separated, e.g.: `123456789,987654321` |
|
||||
|
||||
## Twilio
|
||||
|
||||
Token generation can be found at https://twilio.com/console.
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `TWILIO_ACCOUNT_SID` | Twilio Account SID |
|
||||
| `TWILIO_AUTH_TOKEN` | Twilio Auth Token |
|
||||
| `TWILIO_FROM_NUMBER` | Twilio provided phone number to send messages from |
|
||||
| `TWILIO_TO_NUMBER` | Mobile number to send SMS to |
|
||||
|
||||
???+ note
|
||||
Include country codes in phone numbers. Example: `+4401234567890`
|
||||
|
||||
## Twitter
|
||||
|
||||
Generate all Twitter keys at: https://developer.twitter.com/
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `TWITTER_ACCESS_TOKEN_KEY` | Twitter Token Key |
|
||||
| `TWITTER_ACCESS_TOKEN_SECRET` | Twitter Token Secret |
|
||||
| `TWITTER_CONSUMER_KEY` | Twitter Consumer Key |
|
||||
| `TWITTER_CONSUMER_SECRET` | Twitter Consumer Secret |
|
||||
| `TWITTER_TWEET_TAGS` | List of hashtags to append to the tweet message, e.g.: `#nvidia #nvidiastock` |
|
||||
|
||||
## Twitch
|
||||
|
||||
| Environment variable | Description |
|
||||
|:---:|---|
|
||||
| `TWITCH_CLIENT_ID` | Twitch client ID |
|
||||
| `TWITCH_CLIENT_SECRET`| Twitch client secret |
|
||||
| `TWITCH_ACCESS_TOKEN` | Twitch access token |
|
||||
| `TWITCH_REFRESH_TOKEN` | Twitch refresh token |
|
||||
| `TWITCH_CHANNEL` | Twitch channel |
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
# Project information
|
||||
site_name: streetmerchant
|
||||
site_url: https://jef.codes/streetmerchant
|
||||
site_author: Jef LeCompte
|
||||
site_description: The world's easiest, most powerful stock checker
|
||||
|
||||
# Repository
|
||||
repo_name: jef/streetmerchant
|
||||
repo_url: https://github.com/jef/streetmerchant
|
||||
|
||||
# Copyright
|
||||
copyright: Copyright © 2016 - 2020 Jef LeCompte
|
||||
|
||||
# Configuration
|
||||
theme:
|
||||
favicon: https://raw.githubusercontent.com/jef/streetmerchant/main/docs/assets/images/streetmerchant-square.png
|
||||
features:
|
||||
- search.suggest
|
||||
font:
|
||||
text: Noto Sans
|
||||
code: JetBrains Mono
|
||||
icon:
|
||||
repo: octicons/octoface-24
|
||||
logo: octicons/book-24
|
||||
language: en
|
||||
name: material
|
||||
palette:
|
||||
scheme: default
|
||||
primary: indigo
|
||||
accent: indigo
|
||||
|
||||
# Plugins
|
||||
plugins:
|
||||
- git-revision-date
|
||||
- macros
|
||||
- search
|
||||
|
||||
# Customization
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/jef
|
||||
- icon: fontawesome/brands/twitter
|
||||
link: https://twitter.com/hijxf
|
||||
- icon: fontawesome/brands/linkedin
|
||||
link: https://www.linkedin.com/in/jeflecompte/
|
||||
|
||||
# Extensions
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- attr_list
|
||||
- footnotes
|
||||
- meta
|
||||
- toc:
|
||||
permalink: true
|
||||
- pymdownx.caret
|
||||
- pymdownx.critic
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
- pymdownx.highlight:
|
||||
linenums: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.keys
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.tilde
|
||||
|
||||
# Page tree
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Getting started: getting-started.md
|
||||
- Reference:
|
||||
- Application: reference/application.md
|
||||
- Filter: reference/filter.md
|
||||
- Notification: reference/notification.md
|
||||
- Help:
|
||||
- General: help/general.md
|
||||
- Troubleshoot: help/troubleshoot.md
|
||||
- FAQ: faq.md
|
||||
- Changelog: changelog.md
|
||||
- About: about.md
|
||||
+2
-1
@@ -232,7 +232,8 @@ const notifications = {
|
||||
['tmobile', 'tmomail.net'],
|
||||
['verizon', 'vtext.com'],
|
||||
['virgin', 'vmobl.com'],
|
||||
['virgin-ca', 'vmobile.ca']
|
||||
['virgin-ca', 'vmobile.ca'],
|
||||
['visible', 'vtext.com']
|
||||
]),
|
||||
carrier: envOrArray(process.env.PHONE_CARRIER),
|
||||
number: envOrArray(process.env.PHONE_NUMBER)
|
||||
|
||||
+11
-4
@@ -20,7 +20,9 @@ const prettyJson = winston.format.printf((info) => {
|
||||
export const logger = winston.createLogger({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.metadata({fillExcept: ['level', 'message', 'timestamp']}),
|
||||
winston.format.metadata({
|
||||
fillExcept: ['level', 'message', 'timestamp']
|
||||
}),
|
||||
prettyJson
|
||||
),
|
||||
level: config.logLevel,
|
||||
@@ -129,7 +131,9 @@ export const Print = {
|
||||
'✖ ' +
|
||||
buildProductString(link, store, true) +
|
||||
' :: ' +
|
||||
chalk.yellow(`PRICE ${link.price ?? ''} EXCEEDS LIMIT ${maxPrice}`)
|
||||
chalk.yellow(
|
||||
`PRICE ${link.price ?? ''} EXCEEDS LIMIT ${maxPrice}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,7 +184,8 @@ export const Print = {
|
||||
},
|
||||
productInStock(link: Link): string {
|
||||
let productString = `Product Page: ${link.url}`;
|
||||
if (link.cartUrl) productString += `\nAdd To Cart Link: ${link.cartUrl}`;
|
||||
if (link.cartUrl)
|
||||
productString += `\nAdd To Cart Link: ${link.cartUrl}`;
|
||||
|
||||
return productString;
|
||||
},
|
||||
@@ -204,7 +209,9 @@ function buildSetupString(
|
||||
color?: boolean
|
||||
): string {
|
||||
if (color) {
|
||||
return chalk.cyan(`[${store.name}]`) + chalk.grey(` [setup (${topic})]`);
|
||||
return (
|
||||
chalk.cyan(`[${store.name}]`) + chalk.grey(` [setup (${topic})]`)
|
||||
);
|
||||
}
|
||||
|
||||
return `[${store.name}] [setup (${topic})]`;
|
||||
|
||||
@@ -31,7 +31,7 @@ export function sendDiscordMessage(link: Link, store: Store) {
|
||||
'> provided by [streetmerchant](https://github.com/jef/streetmerchant) with :heart:'
|
||||
)
|
||||
.setThumbnail(
|
||||
'https://raw.githubusercontent.com/jef/streetmerchant/main/media/streetmerchant-square.png'
|
||||
'https://raw.githubusercontent.com/jef/streetmerchant/main/docs/assets/images/streetmerchant-square.png'
|
||||
)
|
||||
.setColor('#52b788')
|
||||
.setTimestamp();
|
||||
@@ -57,7 +57,9 @@ export function sendDiscordMessage(link: Link, store: Store) {
|
||||
});
|
||||
}
|
||||
|
||||
(await Promise.all(promises)).forEach(({client}) => client.destroy());
|
||||
(await Promise.all(promises)).forEach(({client}) =>
|
||||
client.destroy()
|
||||
);
|
||||
|
||||
logger.info('✔ discord message sent');
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -45,13 +45,14 @@ const adjustLightsWithAPI = (hueBridge: Api) => {
|
||||
const arrayOfIDs = lightIds.split(',');
|
||||
arrayOfIDs.forEach((light) => {
|
||||
logger.debug('adjusting all hue lights');
|
||||
(hueBridge.lights.setLightState(light, lightState) as Promise<any>).catch(
|
||||
(error: Error) => {
|
||||
logger.error('Failed to adjust all lights.');
|
||||
logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
(hueBridge.lights.setLightState(
|
||||
light,
|
||||
lightState
|
||||
) as Promise<any>).catch((error: Error) => {
|
||||
logger.error('Failed to adjust all lights.');
|
||||
logger.error(error);
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Adjust all light IDs
|
||||
@@ -102,15 +103,26 @@ export function adjustPhilipsHueLights() {
|
||||
} else if (hue.apiKey && hue.clientId && hue.clientSecret) {
|
||||
logger.info('↗ adjusting Philips Hue lights over cloud');
|
||||
(async () => {
|
||||
logger.debug('Attempting to connect to Philips Hue bridge over cloud');
|
||||
const remoteBootstrap = hueAPI.api.createRemote(clientId, clientSecret);
|
||||
logger.debug(
|
||||
'Attempting to connect to Philips Hue bridge over cloud'
|
||||
);
|
||||
const remoteBootstrap = hueAPI.api.createRemote(
|
||||
clientId,
|
||||
clientSecret
|
||||
);
|
||||
if (hue.accessToken && hue.refreshToken) {
|
||||
remoteBootstrap
|
||||
.connectWithTokens(accessToken, refreshToken, remoteApiUsername)
|
||||
.connectWithTokens(
|
||||
accessToken,
|
||||
refreshToken,
|
||||
remoteApiUsername
|
||||
)
|
||||
.then(
|
||||
(hueBridge) => {
|
||||
adjustLightsWithAPI(hueBridge);
|
||||
logger.info('✔ adjusted Philips Hue lights over cloud');
|
||||
logger.info(
|
||||
'✔ adjusted Philips Hue lights over cloud'
|
||||
);
|
||||
},
|
||||
(error: Error) => {
|
||||
logger.error(
|
||||
|
||||
@@ -4,7 +4,7 @@ import {WebClient} from '@slack/web-api';
|
||||
import {config} from '../config';
|
||||
|
||||
const slack = config.notifications.slack;
|
||||
const channel = slack.channel;
|
||||
const channel = slack.channel.replace('#', '');
|
||||
const token = slack.token;
|
||||
const web = new WebClient(token);
|
||||
|
||||
|
||||
+17
-11
@@ -20,19 +20,25 @@ export function playSound() {
|
||||
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}`);
|
||||
return;
|
||||
}
|
||||
|
||||
player.play(config.notifications.playSound, (error: Error) => {
|
||||
fs.access(
|
||||
config.notifications.playSound,
|
||||
fs.constants.F_OK,
|
||||
(error) => {
|
||||
if (error) {
|
||||
logger.error("✖ couldn't play sound", error);
|
||||
logger.error(
|
||||
`✖ error opening sound file: ${error.message}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('✔ played sound');
|
||||
});
|
||||
});
|
||||
player.play(config.notifications.playSound, (error: Error) => {
|
||||
if (error) {
|
||||
logger.error("✖ couldn't play sound", error);
|
||||
}
|
||||
|
||||
logger.info('✔ played sound');
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ const chatClient: ChatClient = new ChatClient(
|
||||
{
|
||||
accessToken,
|
||||
expiryTimestamp:
|
||||
expiryDate === null ? null : expiryDate.getTime(),
|
||||
expiryDate === null
|
||||
? null
|
||||
: expiryDate.getTime(),
|
||||
refreshToken
|
||||
},
|
||||
null,
|
||||
@@ -90,7 +92,9 @@ export function sendTwitchMessage(link: Link, store: Store) {
|
||||
logger.debug('↗ sending twitch message');
|
||||
|
||||
messages.push(
|
||||
`${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`
|
||||
`${Print.inStock(link, store)}\n${
|
||||
link.cartUrl ? link.cartUrl : link.url
|
||||
}`
|
||||
);
|
||||
|
||||
if (!alreadySaying) {
|
||||
|
||||
@@ -7,7 +7,9 @@ import {usingResponse} from '../util';
|
||||
|
||||
function addNewLinks(store: Store, links: Link[], series: Series) {
|
||||
if (links.length === 0) {
|
||||
logger.debug(Print.message('NO STORE LINKS FOUND', series, store, true));
|
||||
logger.debug(
|
||||
Print.message('NO STORE LINKS FOUND', series, store, true)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -20,7 +22,12 @@ function addNewLinks(store: Store, links: Link[], series: Series) {
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
Print.message(`FOUND ${newLinks.length} STORE LINKS`, series, store, true)
|
||||
Print.message(
|
||||
`FOUND ${newLinks.length} STORE LINKS`,
|
||||
series,
|
||||
store,
|
||||
true
|
||||
)
|
||||
);
|
||||
logger.debug(JSON.stringify(newLinks, null, 2));
|
||||
|
||||
@@ -39,7 +46,9 @@ export async function fetchLinks(store: Store, browser: Browser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug(Print.message('DETECTING STORE LINKS', series, store, true));
|
||||
logger.debug(
|
||||
Print.message('DETECTING STORE LINKS', series, store, true)
|
||||
);
|
||||
|
||||
if (!Array.isArray(url)) {
|
||||
url = [url];
|
||||
@@ -51,12 +60,17 @@ export async function fetchLinks(store: Store, browser: Browser) {
|
||||
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;
|
||||
}
|
||||
|
||||
const docElement = cheerio.load(text).root();
|
||||
const links = store.linksBuilder!.builder(docElement, series);
|
||||
const links = store.linksBuilder!.builder(
|
||||
docElement,
|
||||
series
|
||||
);
|
||||
|
||||
addNewLinks(store, links, series);
|
||||
})
|
||||
|
||||
+4
-1
@@ -29,7 +29,10 @@ function filterModel(model: Link['model'], series: Link['series']): boolean {
|
||||
const sanitizedSeries = series.replace(/\s/g, '');
|
||||
for (const configModelEntry of config.store.showOnlyModels) {
|
||||
const sanitizedConfigModel = configModelEntry.name.replace(/\s/g, '');
|
||||
const sanitizedConfigSeries = configModelEntry.series.replace(/\s/g, '');
|
||||
const sanitizedConfigSeries = configModelEntry.series.replace(
|
||||
/\s/g,
|
||||
''
|
||||
);
|
||||
if (sanitizedConfigSeries) {
|
||||
if (
|
||||
sanitizedSeries === sanitizedConfigSeries &&
|
||||
|
||||
+19
-8
@@ -152,7 +152,8 @@ async function lookup(browser: Browser, store: Store) {
|
||||
|
||||
const proxy = nextProxy(store);
|
||||
|
||||
const useAdBlock = !config.browser.lowBandwidth && !store.disableAdBlocker;
|
||||
const useAdBlock =
|
||||
!config.browser.lowBandwidth && !store.disableAdBlocker;
|
||||
const customContext = config.browser.isIncognito;
|
||||
|
||||
const context = customContext
|
||||
@@ -212,9 +213,9 @@ async function lookup(browser: Browser, store: Store) {
|
||||
statusCode = await lookupCard(browser, store, page, link);
|
||||
} catch (error: unknown) {
|
||||
logger.error(
|
||||
`✖ [${store.name}] ${link.brand} ${link.series} ${link.model} - ${
|
||||
(error as Error).message
|
||||
}`
|
||||
`✖ [${store.name}] ${link.brand} ${link.series} ${
|
||||
link.model
|
||||
} - ${(error as Error).message}`
|
||||
);
|
||||
const client = await page.target().createCDPSession();
|
||||
await client.send('Network.clearBrowserCookies');
|
||||
@@ -266,7 +267,9 @@ async function lookupCard(
|
||||
|
||||
if (await lookupCardInStock(store, page, link)) {
|
||||
const givenUrl =
|
||||
link.cartUrl && config.store.autoAddToCart ? link.cartUrl : link.url;
|
||||
link.cartUrl && config.store.autoAddToCart
|
||||
? link.cartUrl
|
||||
: link.url;
|
||||
logger.info(`${Print.inStock(link, store, true)}\n${givenUrl}`);
|
||||
|
||||
if (config.browser.open) {
|
||||
@@ -313,7 +316,11 @@ async function lookupCardInStock(store: Store, page: Page, link: Link) {
|
||||
|
||||
if (store.labels.bannedSeller) {
|
||||
if (
|
||||
await pageIncludesLabels(page, store.labels.bannedSeller, baseOptions)
|
||||
await pageIncludesLabels(
|
||||
page,
|
||||
store.labels.bannedSeller,
|
||||
baseOptions
|
||||
)
|
||||
) {
|
||||
logger.warn(Print.bannedSeller(link, store, true));
|
||||
return false;
|
||||
@@ -367,7 +374,9 @@ async function lookupCardInStock(store: Store, page: Page, link: Link) {
|
||||
}
|
||||
|
||||
if (store.labels.outOfStock) {
|
||||
if (await pageIncludesLabels(page, store.labels.outOfStock, baseOptions)) {
|
||||
if (
|
||||
await pageIncludesLabels(page, store.labels.outOfStock, baseOptions)
|
||||
) {
|
||||
logger.info(Print.outOfStock(link, store, true));
|
||||
return false;
|
||||
}
|
||||
@@ -378,7 +387,9 @@ async function lookupCardInStock(store: Store, page: Page, link: Link) {
|
||||
|
||||
export async function tryLookupAndLoop(browser: Browser, store: Store) {
|
||||
if (!browser.isConnected()) {
|
||||
logger.debug(`[${store.name}] Ending this loop as browser is disposed...`);
|
||||
logger.debug(
|
||||
`[${store.name}] Ending this loop as browser is disposed...`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ export const AmazonNl: Store = {
|
||||
},
|
||||
{
|
||||
container: '#outOfStock',
|
||||
text: ['we weten niet of en wanneer dit item weer op voorraad is']
|
||||
text: [
|
||||
'we weten niet of en wanneer dit item weer op voorraad is'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -55,13 +55,15 @@ export const Asus: Store = {
|
||||
],
|
||||
name: 'asus',
|
||||
realTimeInventoryLookup: async (itemNumber: string) => {
|
||||
const request_url = 'https://store.asus.com/us/category/get_real_time_data';
|
||||
const request_url =
|
||||
'https://store.asus.com/us/category/get_real_time_data';
|
||||
const response = await fetch(request_url, {
|
||||
body: 'sm_seq_list%5B%5D=' + itemNumber,
|
||||
headers: {
|
||||
'accept-language': 'en-US,en;q=0.9',
|
||||
'cache-control': 'no-cache',
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
'content-type':
|
||||
'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
@@ -3,7 +3,8 @@ import {Store} from './store';
|
||||
export const BestBuy: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '[data-sticky-media-gallery] .fulfillment-add-to-cart-button',
|
||||
container:
|
||||
'[data-sticky-media-gallery] .fulfillment-add-to-cart-button',
|
||||
text: ['add to cart']
|
||||
},
|
||||
maxPrice: {
|
||||
|
||||
@@ -59,7 +59,8 @@ export const Computeruniverse: Store = {
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
series: '3070',
|
||||
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3070-xc3-black'
|
||||
url:
|
||||
'https://www.computeruniverse.net/de/evga-geforce-rtx3070-xc3-black'
|
||||
},
|
||||
{
|
||||
brand: 'gainward',
|
||||
@@ -142,7 +143,8 @@ export const Computeruniverse: Store = {
|
||||
brand: 'pny',
|
||||
model: 'dual fan',
|
||||
series: '3070',
|
||||
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx3070-m-dual-8-gb'
|
||||
url:
|
||||
'https://www.computeruniverse.net/de/pny-geforce-rtx3070-m-dual-8-gb'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
|
||||
@@ -8,7 +8,8 @@ export const Currys: Store = {
|
||||
text: ['add to basket']
|
||||
},
|
||||
maxPrice: {
|
||||
container: '#product-actions span[class*="ProductPriceBlock__Price"]',
|
||||
container:
|
||||
'#product-actions span[class*="ProductPriceBlock__Price"]',
|
||||
euroFormat: false // Note: Currys uses non-euroFromat as price seperator
|
||||
},
|
||||
outOfStock: {
|
||||
|
||||
@@ -28,7 +28,8 @@ export const Ebuyer: Store = {
|
||||
brand: 'sony',
|
||||
model: 'ps5 console',
|
||||
series: 'sonyps5c',
|
||||
url: 'https://www.ebuyer.com/1125329-sony-playstation-5-console-cfi-1015a'
|
||||
url:
|
||||
'https://www.ebuyer.com/1125329-sony-playstation-5-console-cfi-1015a'
|
||||
},
|
||||
{
|
||||
brand: 'sony',
|
||||
|
||||
@@ -12,7 +12,8 @@ export const Elcorteingles: Store = {
|
||||
// },
|
||||
inStock: [
|
||||
{
|
||||
container: '.product_detail-purchase.mb-2.c12 .js-add-cart-text',
|
||||
container:
|
||||
'.product_detail-purchase.mb-2.c12 .js-add-cart-text',
|
||||
text: ['a la cesta']
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,7 +10,8 @@ export const Expert: Store = {
|
||||
}
|
||||
],
|
||||
maxPrice: {
|
||||
container: '.widget-Container-subContent .widget-ArticlePrice-price',
|
||||
container:
|
||||
'.widget-Container-subContent .widget-ArticlePrice-price',
|
||||
euroFormat: false
|
||||
},
|
||||
outOfStock: [
|
||||
@@ -30,7 +31,8 @@ export const Expert: Store = {
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.expert.de/shop/11364114744-ps4-pro-1tb-jet-black.html'
|
||||
url:
|
||||
'https://www.expert.de/shop/11364114744-ps4-pro-1tb-jet-black.html'
|
||||
},
|
||||
{
|
||||
brand: 'sony',
|
||||
|
||||
@@ -33,7 +33,8 @@ export const Game: Store = {
|
||||
brand: 'sony',
|
||||
model: 'ps5 digital',
|
||||
series: 'sonyps5de',
|
||||
url: 'https://www.game.co.uk/en/playstation-5-digital-edition-2826341'
|
||||
url:
|
||||
'https://www.game.co.uk/en/playstation-5-digital-edition-2826341'
|
||||
}
|
||||
],
|
||||
name: 'game'
|
||||
|
||||
@@ -38,7 +38,10 @@ export async function processBackoffDelay(
|
||||
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;
|
||||
|
||||
@@ -23,7 +23,9 @@ export function getProductLinksBuilder(options: LinksBuilderOptions) {
|
||||
const links: Link[] = [];
|
||||
for (let i = 0; i < productElements.length; i++) {
|
||||
const productElement = productElements.eq(i);
|
||||
const titleElement = productElement.find(options.titleSelector).first();
|
||||
const titleElement = productElement
|
||||
.find(options.titleSelector)
|
||||
.first();
|
||||
|
||||
const title = options.titleAttribute
|
||||
? titleElement.attr()?.[options.titleAttribute]
|
||||
|
||||
@@ -62,7 +62,9 @@ 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 🚀🚀🚀`);
|
||||
let lastError: Error | string | undefined;
|
||||
@@ -70,7 +72,9 @@ export class NvidiaCart {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (let i = 0; i < config.nvidia.addToCardAttempts; i++) {
|
||||
try {
|
||||
cartUrl = await this.addToCartAndGetLocationRedirect(productId);
|
||||
cartUrl = await this.addToCartAndGetLocationRedirect(
|
||||
productId
|
||||
);
|
||||
|
||||
break;
|
||||
} catch (error: unknown) {
|
||||
@@ -92,7 +96,9 @@ export class NvidiaCart {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
logger.info(`🚀🚀🚀 [nvidia] ${name}, opening checkout page 🚀🚀🚀`);
|
||||
logger.info(
|
||||
`🚀🚀🚀 [nvidia] ${name}, opening checkout page 🚀🚀🚀`
|
||||
);
|
||||
logger.info(cartUrl);
|
||||
|
||||
await open(cartUrl);
|
||||
@@ -129,7 +135,9 @@ export class NvidiaCart {
|
||||
this.browser,
|
||||
this.sessionUrl,
|
||||
async (response) => {
|
||||
return response?.json() as NvidiaSessionTokenJSON | undefined;
|
||||
return response?.json() as
|
||||
| NvidiaSessionTokenJSON
|
||||
| undefined;
|
||||
}
|
||||
);
|
||||
if (
|
||||
@@ -154,7 +162,8 @@ export class NvidiaCart {
|
||||
protected async addToCartAndGetLocationRedirect(
|
||||
productId: number
|
||||
): Promise<string> {
|
||||
const url = 'https://api-prod.nvidia.com/direct-sales-shop/DR/add-to-cart';
|
||||
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}`);
|
||||
|
||||
@@ -67,7 +67,10 @@ export function generateLinks(): Link[] {
|
||||
links.push({
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
openCartAction: generateOpenCartAction(fe2060SuperId, 'TEST CARD debug'),
|
||||
openCartAction: generateOpenCartAction(
|
||||
fe2060SuperId,
|
||||
'TEST CARD debug'
|
||||
),
|
||||
series: 'test:series',
|
||||
url: nvidiaStockUrl(fe2060SuperId, drLocale, currency)
|
||||
});
|
||||
|
||||
@@ -208,7 +208,9 @@ function printConfig() {
|
||||
}
|
||||
|
||||
if (config.store.showOnlyBrands.length > 0) {
|
||||
logger.info(`ℹ selected brands: ${config.store.showOnlyBrands.join(', ')}`);
|
||||
logger.info(
|
||||
`ℹ selected brands: ${config.store.showOnlyBrands.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (config.store.showOnlyModels.length > 0) {
|
||||
@@ -224,7 +226,9 @@ function printConfig() {
|
||||
}
|
||||
|
||||
if (config.store.showOnlySeries.length > 0) {
|
||||
logger.info(`ℹ selected series: ${config.store.showOnlySeries.join(', ')}`);
|
||||
logger.info(
|
||||
`ℹ selected series: ${config.store.showOnlySeries.join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ export const NeweggCa: Store = {
|
||||
text: ['add to cart']
|
||||
},
|
||||
maxPrice: {
|
||||
container: 'div#app div.product-price > ul > li.price-current > strong',
|
||||
container:
|
||||
'div#app div.product-price > ul > li.price-current > strong',
|
||||
euroFormat: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -271,7 +271,8 @@ export const Notebooksbilliger: Store = {
|
||||
brand: 'amd',
|
||||
model: '5800x',
|
||||
series: 'ryzen5800',
|
||||
url: 'https://www.notebooksbilliger.de/amd+ryzen+ryzen+7+5800x+cpu+684018'
|
||||
url:
|
||||
'https://www.notebooksbilliger.de/amd+ryzen+ryzen+7+5800x+cpu+684018'
|
||||
},
|
||||
{
|
||||
brand: 'amd',
|
||||
|
||||
@@ -28,7 +28,8 @@ export const Nvidia: Store = {
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.nvidia.com/en-us/geforce/graphics-cards/rtx-2060-super/'
|
||||
url:
|
||||
'https://www.nvidia.com/en-us/geforce/graphics-cards/rtx-2060-super/'
|
||||
},
|
||||
{
|
||||
brand: 'nvidia',
|
||||
|
||||
@@ -15,7 +15,9 @@ export const Otto: Store = {
|
||||
},
|
||||
outOfStock: {
|
||||
container: 'div.p_message.p_message--hint > strong',
|
||||
text: ['Deinen gewünschten Artikel können wir leider nicht mehr liefern']
|
||||
text: [
|
||||
'Deinen gewünschten Artikel können wir leider nicht mehr liefern'
|
||||
]
|
||||
}
|
||||
},
|
||||
links: [
|
||||
|
||||
@@ -41,7 +41,8 @@ export const PCComponentes: Store = {
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3080',
|
||||
url: 'https://www.pccomponentes.com/asus-tuf-geforce-rtx-3080-10gb-gddr6x'
|
||||
url:
|
||||
'https://www.pccomponentes.com/asus-tuf-geforce-rtx-3080-10gb-gddr6x'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
@@ -246,7 +247,8 @@ export const PCComponentes: Store = {
|
||||
brand: 'asus',
|
||||
model: 'dual',
|
||||
series: '3070',
|
||||
url: 'https://www.pccomponentes.com/asus-geforce-rtx-3070-dual-8gb-gddr6'
|
||||
url:
|
||||
'https://www.pccomponentes.com/asus-geforce-rtx-3070-dual-8gb-gddr6'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
|
||||
@@ -32,13 +32,17 @@ export const Very: Store = {
|
||||
const links: Link[] = [];
|
||||
for (let i = 0; i < productElements.length; i++) {
|
||||
const productElement = productElements.eq(i);
|
||||
const titleElement = productElement.find('.productTitle').first();
|
||||
const titleElement = productElement
|
||||
.find('.productTitle')
|
||||
.first();
|
||||
const title = titleElement.text()?.replace(/\n/g, ' ').trim();
|
||||
|
||||
if (
|
||||
!title ||
|
||||
['RTX', series]
|
||||
.map((x) => title.toLowerCase().includes(x.toLowerCase()))
|
||||
.map((x) =>
|
||||
title.toLowerCase().includes(x.toLowerCase())
|
||||
)
|
||||
.filter((x) => !x).length > 0
|
||||
) {
|
||||
continue;
|
||||
|
||||
@@ -23,25 +23,29 @@ export const VsGamers: Store = {
|
||||
brand: 'amd',
|
||||
model: '5600x',
|
||||
series: 'ryzen5600',
|
||||
url: 'https://www.vsgamers.es/product/procesador-amd-ryzen-5-5600x-37-ghz'
|
||||
url:
|
||||
'https://www.vsgamers.es/product/procesador-amd-ryzen-5-5600x-37-ghz'
|
||||
},
|
||||
{
|
||||
brand: 'amd',
|
||||
model: '5800x',
|
||||
series: 'ryzen5800',
|
||||
url: 'https://www.vsgamers.es/product/procesador-amd-ryzen-7-5800x-38-ghz'
|
||||
url:
|
||||
'https://www.vsgamers.es/product/procesador-amd-ryzen-7-5800x-38-ghz'
|
||||
},
|
||||
{
|
||||
brand: 'amd',
|
||||
model: '5900x',
|
||||
series: 'ryzen5900',
|
||||
url: 'https://www.vsgamers.es/product/procesador-amd-ryzen-9-5900x-37-ghz'
|
||||
url:
|
||||
'https://www.vsgamers.es/product/procesador-amd-ryzen-9-5900x-37-ghz'
|
||||
},
|
||||
{
|
||||
brand: 'amd',
|
||||
model: '5950x',
|
||||
series: 'ryzen5950',
|
||||
url: 'https://www.vsgamers.es/product/procesador-amd-ryzen-9-5950x-34-ghz'
|
||||
url:
|
||||
'https://www.vsgamers.es/product/procesador-amd-ryzen-9-5950x-34-ghz'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
|
||||
@@ -3,7 +3,8 @@ import {Store} from './store';
|
||||
export const Walmart: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '.button.spin-button.prod-ProductCTA--primary.button--primary',
|
||||
container:
|
||||
'.button.spin-button.prod-ProductCTA--primary.button--primary',
|
||||
text: ['add to cart']
|
||||
},
|
||||
maxPrice: {
|
||||
|
||||
@@ -20,7 +20,8 @@ export const Wipoid: Store = {
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.wipoid.com/pny-geforce-rtx-1650-dual-fan-4gb-gddr6.html'
|
||||
url:
|
||||
'https://www.wipoid.com/pny-geforce-rtx-1650-dual-fan-4gb-gddr6.html'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
|
||||
Vendored
+4
-1
@@ -9,7 +9,10 @@ declare module 'play-sound' {
|
||||
export interface PlaySound {
|
||||
player: string;
|
||||
|
||||
play: ((file: string, callback: (error: Error) => void) => PlayerProcess) &
|
||||
play: ((
|
||||
file: string,
|
||||
callback: (error: Error) => void
|
||||
) => PlayerProcess) &
|
||||
((
|
||||
file: string,
|
||||
options: PlayOptions,
|
||||
|
||||
Vendored
+20
-5
@@ -18,7 +18,10 @@ declare module '@jef/pushbullet' {
|
||||
export interface PushBulletStream {
|
||||
connect: () => void;
|
||||
close: () => void;
|
||||
on: ((event: 'connect' | 'close' | 'nop', callback: () => void) => void) &
|
||||
on: ((
|
||||
event: 'connect' | 'close' | 'nop',
|
||||
callback: () => void
|
||||
) => void) &
|
||||
((event: 'error', callback: (error: any) => void) => void) &
|
||||
((event: 'message', callback: (message: any) => void) => void) &
|
||||
((event: 'tickle', callback: (tickle: any) => void) => void) &
|
||||
@@ -30,7 +33,10 @@ declare module '@jef/pushbullet' {
|
||||
me(callback: PushBulletCallback);
|
||||
devices(options: ListOptions, callback: PushBulletCallback);
|
||||
devices(callback: PushBulletCallback);
|
||||
createDevice(options: Record<string, any>, callback: PushBulletCallback);
|
||||
createDevice(
|
||||
options: Record<string, any>,
|
||||
callback: PushBulletCallback
|
||||
);
|
||||
updateDevice(
|
||||
deviceIden: string,
|
||||
deviceOptions: Record<string, any>,
|
||||
@@ -65,8 +71,14 @@ declare module '@jef/pushbullet' {
|
||||
subscriptions(callback: PushBulletCallback);
|
||||
subscribe(channelTag: string, callback: PushBulletCallback);
|
||||
unsubscribe(subscriptionIden: string, callback: PushBulletCallback);
|
||||
muteSubscription(subscriptionIden: string, callback: PushBulletCallback);
|
||||
unmuteSubscription(subscriptionIden: string, callback: PushBulletCallback);
|
||||
muteSubscription(
|
||||
subscriptionIden: string,
|
||||
callback: PushBulletCallback
|
||||
);
|
||||
unmuteSubscription(
|
||||
subscriptionIden: string,
|
||||
callback: PushBulletCallback
|
||||
);
|
||||
channelInfo(channelTag: string, callback: PushBulletCallback);
|
||||
chats(options: ListOptions, callback: PushBulletCallback);
|
||||
chats(callback: PushBulletCallback);
|
||||
@@ -75,7 +87,10 @@ declare module '@jef/pushbullet' {
|
||||
muteChat(chatIden: string, callback: PushBulletCallback);
|
||||
unmuteChat(chatIden: string, callback: PushBulletCallback);
|
||||
sendSMS(options: Record<string, any>, callback: PushBulletCallback);
|
||||
sendClipboard(options: Record<string, any>, callback: PushBulletCallback);
|
||||
sendClipboard(
|
||||
options: Record<string, any>,
|
||||
callback: PushBulletCallback
|
||||
);
|
||||
dismissEphemeral(
|
||||
options: Record<string, any>,
|
||||
callback: PushBulletCallback
|
||||
|
||||
+3
-1
@@ -6,7 +6,9 @@ import {logger} from './logger';
|
||||
|
||||
export function getSleepTime(store: Store) {
|
||||
const minSleep = store.minPageSleep as number;
|
||||
return minSleep + Math.random() * ((store.maxPageSleep as number) - minSleep);
|
||||
return (
|
||||
minSleep + Math.random() * ((store.maxPageSleep as number) - minSleep)
|
||||
);
|
||||
}
|
||||
|
||||
export async function delay(ms: number) {
|
||||
|
||||
Reference in New Issue
Block a user