mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 08:47:43 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9ed774595 | |||
| c53cfd67c1 | |||
| e1b34a9ccf | |||
| d6a27c988c | |||
| 8a70f14743 | |||
| 02ea40b6b2 | |||
| 47dab22d57 | |||
| 676e793ef3 | |||
| dd45dba82c | |||
| 482fb58cbf | |||
| 8913879593 | |||
| 9a53917586 | |||
| ab61a98bea | |||
| 9f470f06e9 | |||
| 4cb52d31d5 | |||
| 6aed674ee1 | |||
| bcbd2b6007 | |||
| 76b28a6dbd | |||
| a154e95681 | |||
| 0ad67fe204 | |||
| 2bd8446960 | |||
| 257dd0e615 | |||
| c78d9a98ba | |||
| 7c50e2b5aa | |||
| 8466f9f398 | |||
| 22fd22fe74 | |||
| 98c8b1e7b8 | |||
| 74490eae3a | |||
| 20656805c1 | |||
| 6608a79769 | |||
| a70e63bb35 | |||
| 908ed35882 | |||
| 9d5f430119 | |||
| ede9f6f5a6 | |||
| 136ccf3593 | |||
| 9d66683528 | |||
| cf0eac2b23 | |||
| 6409646d57 | |||
| 605bdd7ca7 | |||
| 8e7d267549 | |||
| 190388cfe4 | |||
| 1d63733681 | |||
| 78758552b2 | |||
| 14c36bebe7 | |||
| 327f7b080a | |||
| 4f7db895e2 | |||
| 3b51c02873 | |||
| ebd6091a09 | |||
| 3b9a1d2ea8 | |||
| adb603b776 | |||
| 2c6866ad7f | |||
| 71c6774511 | |||
| 5f628d2c12 | |||
| 4dfdb9eb2e | |||
| 12d25eb710 | |||
| 9675c5b8d6 | |||
| 0df2dcfbd4 | |||
| 76f5849889 | |||
| 6413144c1c | |||
| 133a54fa17 | |||
| debd8f57da | |||
| 3ea146da14 | |||
| 722eaf3cd6 | |||
| 7191e03a80 | |||
| 0f6e570cc8 | |||
| 7fc1e776fd | |||
| a75d214dd5 | |||
| 19c8f188c7 | |||
| d42736bf22 | |||
| d2893f6826 | |||
| 07b2da4fe9 | |||
| 770a13ac35 | |||
| d9be3fe618 | |||
| 252459d5d3 | |||
| 7efdcd8f07 | |||
| 4f83b3b233 | |||
| c0352961a9 | |||
| dc0f710674 | |||
| 2e5d13bda5 | |||
| 2a1f15041e | |||
| a3fc07daf0 | |||
| 5b91065043 | |||
| e819e46116 | |||
| 39a478050a | |||
| 28947be9bc | |||
| 3de1f81eb1 | |||
| d1a5aa1f02 | |||
| a501cf703b | |||
| c65fa04666 | |||
| a538809db5 | |||
| 85faaa1ca7 | |||
| 103d96dc81 | |||
| df5ba68e94 | |||
| b7d9462e79 | |||
| 1e9d8fec42 |
+9
-2
@@ -1,5 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
+38
-17
@@ -1,18 +1,39 @@
|
||||
EMAIL_USERNAME="youremail@gmail.com"
|
||||
EMAIL_PASSWORD="secretpassword"
|
||||
NOTIFICATION_TEST="false"
|
||||
PAGE_TIMEOUT="30000"
|
||||
RATE_LIMIT_TIMEOUT="5000"
|
||||
SLACK_CHANNEL="SlackChannelName"
|
||||
SLACK_TOKEN="slack-token"
|
||||
STORES="bestbuy,bandh,nvidia"
|
||||
PHONE_NUMBER="1234567890"
|
||||
PHONE_CARRIER="tmobile"
|
||||
PUSHOVER_TOKEN="123pushover-token456"
|
||||
PUSHOVER_USER="123pushover-user-key"
|
||||
OPEN_BROWSER="true"
|
||||
PLAY_SOUND="false"
|
||||
SCREENSHOT="true"
|
||||
# ** All configuration variables are optional **
|
||||
# Read https://github.com/jef/nvidia-snatcher#customization for help on customizing this file
|
||||
#############################################################################################
|
||||
|
||||
BROWSER_TRUSTED=""
|
||||
DISCORD_NOTIFY_GROUP=""
|
||||
DISCORD_WEB_HOOK=""
|
||||
EMAIL_USERNAME=""
|
||||
EMAIL_PASSWORD=""
|
||||
HEADLESS=""
|
||||
IN_STOCK_WAIT_TIME=""
|
||||
LOG_LEVEL=""
|
||||
MICROCENTER_LOCATION=""
|
||||
OPEN_BROWSER=""
|
||||
PAGE_TIMEOUT=""
|
||||
PHONE_NUMBER=""
|
||||
PHONE_CARRIER=""
|
||||
PLAY_SOUND=""
|
||||
PUSHBULLET=""
|
||||
PUSHOVER_TOKEN=""
|
||||
PUSHOVER_USER=""
|
||||
PAGE_SLEEP_MIN=""
|
||||
PAGE_SLEEP_MAX=""
|
||||
SHOW_ONLY_BRANDS=""
|
||||
SHOW_ONLY_MODELS=""
|
||||
SHOW_ONLY_SERIES=""
|
||||
SLACK_CHANNEL=""
|
||||
SLACK_TOKEN=""
|
||||
STORES=""
|
||||
COUNTRY=""
|
||||
SCREENSHOT="false"
|
||||
TELEGRAM_ACCESS_TOKEN=""
|
||||
TELEGRAM_CHAT_ID="1234"
|
||||
SHOW_ONLY_BRANDS="evga"
|
||||
TELEGRAM_CHAT_ID=""
|
||||
TWITTER_CONSUMER_KEY=""
|
||||
TWITTER_CONSUMER_SECRET=""
|
||||
TWITTER_ACCESS_TOKEN_KEY=""
|
||||
TWITTER_ACCESS_TOKEN_SECRET=""
|
||||
TWITTER_TWEET_TAGS=""
|
||||
USER_AGENT=""
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
github: jef
|
||||
patreon: hijef
|
||||
custom: ["https://www.paypal.me/jxf"]
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<!-- Fixes #(issue) -->
|
||||
<!-- Please also include relevant motivation and context. -->
|
||||
|
||||
<!-- Feel free to include your Discord tag here and we'll consider making you a ringer! -->
|
||||
<!-- Continuous improvements may also grant you contributor access. -->
|
||||
|
||||
### Testing
|
||||
|
||||
<!-- Please describe the tests that you ran to verify your changes. -->
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
# Changelog
|
||||
|
||||
## [1.5.0](https://www.github.com/jef/nvidia-snatcher/compare/v1.4.0...v1.5.0) (2020-09-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* filter models ([#261](https://www.github.com/jef/nvidia-snatcher/issues/261)) ([e1b34a9](https://www.github.com/jef/nvidia-snatcher/commit/e1b34a9ccfa45fa1a11da9af9074059b6084904b))
|
||||
* **log:** colors for console logs ([#207](https://www.github.com/jef/nvidia-snatcher/issues/207)) ([0ad67fe](https://www.github.com/jef/nvidia-snatcher/commit/0ad67fe20453898ce0a6b5faff00062735411119))
|
||||
* **notification:** add desktop notifications ([#140](https://www.github.com/jef/nvidia-snatcher/issues/140)) ([722eaf3](https://www.github.com/jef/nvidia-snatcher/commit/722eaf3cd680c4600b79f842c6c5acdb9e51ad71))
|
||||
* **notification:** add pushbullet, add url with notifications ([#226](https://www.github.com/jef/nvidia-snatcher/issues/226)) ([74490ea](https://www.github.com/jef/nvidia-snatcher/commit/74490eae3ab30de7d7a708d5dd970e070f27f2ea))
|
||||
* **notification:** twitter integration ([#224](https://www.github.com/jef/nvidia-snatcher/issues/224)) ([908ed35](https://www.github.com/jef/nvidia-snatcher/commit/908ed358826f9de530f5892ded1a54964a304d15))
|
||||
* **store:** add `bannedSeller` label for stores ([#173](https://www.github.com/jef/nvidia-snatcher/issues/173)) ([71c6774](https://www.github.com/jef/nvidia-snatcher/commit/71c6774511f7ba13d34d2e40b69abf52d06e6225))
|
||||
* **store:** add amazon-de ([#167](https://www.github.com/jef/nvidia-snatcher/issues/167)) ([8a70f14](https://www.github.com/jef/nvidia-snatcher/commit/8a70f147438584cc334710bc66220d05eb32fcbd))
|
||||
* **store:** add bestbuy.ca ([#229](https://www.github.com/jef/nvidia-snatcher/issues/229)) ([22fd22f](https://www.github.com/jef/nvidia-snatcher/commit/22fd22fe743d3e286eae3430aecd6e7a0a5de8c0))
|
||||
* **store:** add evga eu ([#172](https://www.github.com/jef/nvidia-snatcher/issues/172)) ([605bdd7](https://www.github.com/jef/nvidia-snatcher/commit/605bdd7ca73c585734f6c5df1a86f4fbfbff9163))
|
||||
* **store:** add evga model ([#220](https://www.github.com/jef/nvidia-snatcher/issues/220)) ([190388c](https://www.github.com/jef/nvidia-snatcher/commit/190388cfe4a5e3f19abccd0ff786f654b9a04d2f))
|
||||
* **store:** add microcenter store location config ([#215](https://www.github.com/jef/nvidia-snatcher/issues/215)) ([d6a27c9](https://www.github.com/jef/nvidia-snatcher/commit/d6a27c988c7b1011c7a10084d8283a60ed8aea5c))
|
||||
* **stores:** add 3090 for bestbuy, newegg ([#249](https://www.github.com/jef/nvidia-snatcher/issues/249)) ([dd45dba](https://www.github.com/jef/nvidia-snatcher/commit/dd45dba82cb86f7e7664298dd202b93bbbd46d9f))
|
||||
* **stores:** add 3090s for amazon-ca, bestbuy-ca, newegg-ca ([#258](https://www.github.com/jef/nvidia-snatcher/issues/258)) ([482fb58](https://www.github.com/jef/nvidia-snatcher/commit/482fb58cbfde6f95fb6f77de790d76e6aa2a5926))
|
||||
* add chromium sandbox skipping ([#209](https://www.github.com/jef/nvidia-snatcher/issues/209)) ([2065680](https://www.github.com/jef/nvidia-snatcher/commit/20656805c1259637bb3a4db465a8d16d4780296a))
|
||||
* deprecate nvidia (api), add 3080 add 3090 ([9f470f0](https://www.github.com/jef/nvidia-snatcher/commit/9f470f06e9e9fb605d340c0b0f9016d7288e8c0b))
|
||||
* invert logic ([#141](https://www.github.com/jef/nvidia-snatcher/issues/141)) ([6608a79](https://www.github.com/jef/nvidia-snatcher/commit/6608a79769ff03543ab4ed2f2cead3410d7d7e99))
|
||||
* multiple discord roles and webhooks, qol for envs ([#260](https://www.github.com/jef/nvidia-snatcher/issues/260)) ([8913879](https://www.github.com/jef/nvidia-snatcher/commit/8913879593252c9c83020b2e2c46bad7537b2a20))
|
||||
* **store:** add newegg.ca ([#160](https://www.github.com/jef/nvidia-snatcher/issues/160)) ([76f5849](https://www.github.com/jef/nvidia-snatcher/commit/76f584988979a40269fd3641e996800a63b4b163)), closes [#159](https://www.github.com/jef/nvidia-snatcher/issues/159)
|
||||
* **store:** add office depot ([#157](https://www.github.com/jef/nvidia-snatcher/issues/157)) ([0df2dcf](https://www.github.com/jef/nvidia-snatcher/commit/0df2dcfbd48235fba7126d96cd912634c5b4fdd9))
|
||||
* **store:** add zotac store ([#214](https://www.github.com/jef/nvidia-snatcher/issues/214)) ([7875855](https://www.github.com/jef/nvidia-snatcher/commit/78758552b22e608dbdf3e76397f5b5efb893fef5))
|
||||
* add delay on captcha to try and evade faster ([#119](https://www.github.com/jef/nvidia-snatcher/issues/119)) ([4f83b3b](https://www.github.com/jef/nvidia-snatcher/commit/4f83b3b233657841a4068a8ff9dd6c8dbff631c0))
|
||||
* bestbuy bypass international splash, newegg add to cart ([#153](https://www.github.com/jef/nvidia-snatcher/issues/153)) ([133a54f](https://www.github.com/jef/nvidia-snatcher/commit/133a54fa170bb16dd26b0d72b1a02c56b3851b7f))
|
||||
* card series filter, fix: newegg `oosLabels` ([#120](https://www.github.com/jef/nvidia-snatcher/issues/120)) ([252459d](https://www.github.com/jef/nvidia-snatcher/commit/252459d5d3de2b8cb25deee9ae318108e3dda2be))
|
||||
* custom user agent ([#121](https://www.github.com/jef/nvidia-snatcher/issues/121)) ([d9be3fe](https://www.github.com/jef/nvidia-snatcher/commit/d9be3fe6183eaa9694b186c7a75e1f28bb31dace))
|
||||
* include screenshot for emails + sms notifications ([#144](https://www.github.com/jef/nvidia-snatcher/issues/144)) ([7191e03](https://www.github.com/jef/nvidia-snatcher/commit/7191e03a80e577b59b2861289aa658cfa0ffc0fa))
|
||||
* load puppeteer faster, run stores in parallel ([#83](https://www.github.com/jef/nvidia-snatcher/issues/83)) ([d1a5aa1](https://www.github.com/jef/nvidia-snatcher/commit/d1a5aa1f02ff0a8f293b93e3c078b5943908a95b))
|
||||
* set country in config, login to nvidia when starting ([#162](https://www.github.com/jef/nvidia-snatcher/issues/162)) ([ebd6091](https://www.github.com/jef/nvidia-snatcher/commit/ebd6091a09fb5e52a66742767ae4b58323cd7447))
|
||||
* temporarily pause requests if store has stock ([#147](https://www.github.com/jef/nvidia-snatcher/issues/147)) ([6413144](https://www.github.com/jef/nvidia-snatcher/commit/6413144c1cae89f33f852cc93870b407a784f2bb))
|
||||
* update for complex add to cart, fix nvidia ([#108](https://www.github.com/jef/nvidia-snatcher/issues/108)) ([3ea146d](https://www.github.com/jef/nvidia-snatcher/commit/3ea146da14ea40d145ccfc05436beeb0a9fed8d9))
|
||||
* **notification:** discord integration ([#82](https://www.github.com/jef/nvidia-snatcher/issues/82)) ([a3fc07d](https://www.github.com/jef/nvidia-snatcher/commit/a3fc07daf0a3f33f18e03d4cfc13d3477a9c4fa0))
|
||||
* **scraping:** change lookup impl, add randomize sleep ([#110](https://www.github.com/jef/nvidia-snatcher/issues/110)) ([dc0f710](https://www.github.com/jef/nvidia-snatcher/commit/dc0f7106749b0afa0ff1c91cabb90b65be30e909))
|
||||
* **store:** add adorama ([#104](https://www.github.com/jef/nvidia-snatcher/issues/104)) ([5b91065](https://www.github.com/jef/nvidia-snatcher/commit/5b910650430ad4806b22722efa9a013e72ea47e7))
|
||||
* **store:** add asus ([#102](https://www.github.com/jef/nvidia-snatcher/issues/102)) ([a501cf7](https://www.github.com/jef/nvidia-snatcher/commit/a501cf703bb05f47af6240a4b16a3dc4dcf3baf5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **store:** adorama captcha config ([#234](https://www.github.com/jef/nvidia-snatcher/issues/234)) ([9a53917](https://www.github.com/jef/nvidia-snatcher/commit/9a539175860f98de3b023009f751e59d94f0aaef))
|
||||
* color logs and notification ([76b28a6](https://www.github.com/jef/nvidia-snatcher/commit/76b28a6dbdf5480c12a8c82b031c3f2880d17b11))
|
||||
* **notification:** change discord ping visibility ([#168](https://www.github.com/jef/nvidia-snatcher/issues/168)) ([9675c5b](https://www.github.com/jef/nvidia-snatcher/commit/9675c5b8d61226db4652964e7f1e7399bb82d04e))
|
||||
* **store:** bandh removed cards ([#201](https://www.github.com/jef/nvidia-snatcher/issues/201)) ([6409646](https://www.github.com/jef/nvidia-snatcher/commit/6409646d57bf2b2bb5a4bcf8239740abed8edafb))
|
||||
* `rateLimitTimeout` not being defaulted ([#106](https://www.github.com/jef/nvidia-snatcher/issues/106)) ([28947be](https://www.github.com/jef/nvidia-snatcher/commit/28947be9bc8981d7a45a5d0e69c18d039fcd9ed3))
|
||||
* check response for rate limiting ([#58](https://www.github.com/jef/nvidia-snatcher/issues/58)) ([#98](https://www.github.com/jef/nvidia-snatcher/issues/98)) ([b7d9462](https://www.github.com/jef/nvidia-snatcher/commit/b7d9462e794ef3961fb57c79ef8f66e77d25d20a))
|
||||
* keep single `Store` from draining ([e819e46](https://www.github.com/jef/nvidia-snatcher/commit/e819e46116d4e0b067a59791094b5cfbd2d7cd45))
|
||||
* memory leak due to adblocker ([#139](https://www.github.com/jef/nvidia-snatcher/issues/139)) ([0f6e570](https://www.github.com/jef/nvidia-snatcher/commit/0f6e570cc817dfc10bcddc5743a0faf3b1489270))
|
||||
* **nvidia:** false positives ([#132](https://www.github.com/jef/nvidia-snatcher/issues/132)) ([a75d214](https://www.github.com/jef/nvidia-snatcher/commit/a75d214dd555d5e0388cb54b15be324cc25b6a15))
|
||||
* newegg out-of-stock ([#124](https://www.github.com/jef/nvidia-snatcher/issues/124)) ([770a13a](https://www.github.com/jef/nvidia-snatcher/commit/770a13ac3559401b430547908d1df014582c1e37))
|
||||
* newegg out-of-stock labels ([#134](https://www.github.com/jef/nvidia-snatcher/issues/134)) ([19c8f18](https://www.github.com/jef/nvidia-snatcher/commit/19c8f188c796258c469c2b4c6461fc5da3907a47))
|
||||
* **notification:** wrong condition for sounds playing ([#91](https://www.github.com/jef/nvidia-snatcher/issues/91)) ([103d96d](https://www.github.com/jef/nvidia-snatcher/commit/103d96dc81d6fd097fcdbed5bdd7487d7d73bf6e))
|
||||
* **store:** false positives for nvidia. ([#85](https://www.github.com/jef/nvidia-snatcher/issues/85)) ([c65fa04](https://www.github.com/jef/nvidia-snatcher/commit/c65fa04666775060532e28076a0b4af50f8dd30b))
|
||||
|
||||
## [1.4.0](https://www.github.com/jef/nvidia-snatcher/compare/v1.3.0...v1.4.0) (2020-09-19)
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[FAQ](#FAQ) | [Issues](https://github.com/jef/nvidia-snatcher/issues) | [Wiki](https://github.com/jef/nvidia-snatcher/wiki)
|
||||
|
||||

|
||||
|
||||
The purpose of this bot is to get an Nvidia card. It tries multiple things to do that.
|
||||
|
||||
- Currently, `nvidia-snatcher` is not capable of purchasing a card for you
|
||||
@@ -28,11 +30,11 @@ The purpose of this bot is to get an Nvidia card. It tries multiple things to do
|
||||
|
||||
> :point_right: You may get false positives from time to time, so I apologize for that. The library currently waits for all calls to be completed before parsing, but sometimes this can have unknown behavior. Patience is a virtue :)
|
||||
|
||||
| | **Amazon** | **EVGA** | **Best Buy** | **B&H** | **Micro Center** | **Newegg** | **Nvidia** |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **3070**| | | | | | | |
|
||||
| **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` |
|
||||
| **3090** | | | | | | | |
|
||||
| | **Adorama** | **Amazon** | **Amazon (CA)** | **ASUS** | **B&H** | **Best Buy** | **Best Buy (CA)** | **EVGA** | **Micro Center** | **Newegg** | **Newegg (CA)** | **Nvidia** | **Office Depot** | **Zotac** |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **3070**| | | | | | | | | | | | | | |
|
||||
| **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` |
|
||||
| **3090** | | | | | | `✔` | `✔` | | | `✔` | `✔` | `✔` | | |
|
||||
|
||||
## Installation and prerequisites
|
||||
|
||||
@@ -56,57 +58,119 @@ At any point you want the program to stop, use <kbd>Ctrl</kbd> + <kbd>C</kbd>.
|
||||
|
||||
### Customization
|
||||
|
||||
To customize `nvidia-snatcher`, make a copy of `.env-example` as `.env` and make any changes to your liking.
|
||||
To customize `nvidia-snatcher`, make a copy of `.env-example` as `.env` and make any changes to your liking. _All environment variables are **optional**._
|
||||
|
||||
Here is a list of variables that you can use to customize your newly copied `.env` file:
|
||||
|
||||
| **Environment variable** | **Description** |
|
||||
|:---:|---|
|
||||
| `EMAIL_USERNAME` | Gmail address (e.g., `jensen.robbed.us@gmail.com`); optional |
|
||||
| `EMAIL_PASSWORD` | Gmail password; see below if you have MFA; optional |
|
||||
| `NOTIFICATION_TEST` | Test all the notifications configured; optional, default: `false` |
|
||||
| `PAGE_TIMEOUT` | Navigation Timeout in milliseconds (`0` for infinite); optional, default: `30000` |
|
||||
| `PHONE_NUMBER` | 10 digit phone number (e.g., `1234567890`); optional, email configuration required |
|
||||
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS; optional, email configuration required |
|
||||
| `RATE_LIMIT_TIMEOUT` | Rate limit timeout for each full store cycle; optional, default: `5000` |
|
||||
| `SHOW_ONLY_BRANDS` | If set, will only show specified brands, seperated by `,` |
|
||||
| `SLACK_CHANNEL` | Slack channel for posting (e.g., `update`); optional |
|
||||
| `SLACK_TOKEN` | Slack API token; optional |
|
||||
| `STORES` | [Supported stores](#supported-stores) you want to be scraped; optional, default: `nvidia` |
|
||||
| `OPEN_BROWSER` | Toggle for whether or not the browser should open when item is found, default: `true` |
|
||||
| `PLAY_SOUND` | Play this sound notification if a card is found.; optional |
|
||||
| `SCREENSHOT` | Capture screenshot of page on successful hit; optional, default `true` |
|
||||
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token; optional |
|
||||
| `TELEGRAM_CHAT_ID` | Telegram chat ID; optional |
|
||||
| **Environment variable** | **Description** | **Notes** |
|
||||
|:---:|---|---|
|
||||
| `BROWSER_TRUSTED` | Skip Chromium Sandbox | Useful for containerized environments, default: `false` |
|
||||
| `DESKTOP_NOTIFICATIONS` | Display desktop notifications using [node-notifier](https://www.npmjs.com/package/node-notifier) | Default: `false` |
|
||||
| `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 |
|
||||
| `EMAIL_USERNAME` | Gmail address | E.g.: `jensen.robbed.us@gmail.com` |
|
||||
| `EMAIL_PASSWORD` | Gmail password | See below if you have MFA |
|
||||
| `HEADLESS` | Puppeteer to run headless or not | Debugging related, default: `true` |
|
||||
| `IN_STOCK_WAIT_TIME` | Time to wait between requests to the same store if it has cards in stock | In seconds, default: `0` |
|
||||
| `LOG_LEVEL` | [Logging levels](https://github.com/winstonjs/winston#logging-levels) | Debugging related, default: `info` |
|
||||
| `MICROCENTER_LOCATION` | Specific MicroCenter location to search | Default : `web` |
|
||||
| `OPEN_BROWSER` | Toggle for whether or not the browser should open when item is found | Default: `true` |
|
||||
| `PAGE_TIMEOUT` | Navigation Timeout in milliseconds | `0` for infinite, default: `30000` |
|
||||
| `PHONE_NUMBER` | 10 digit phone number | E.g.: `1234567890`, email configuration required |
|
||||
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS | Email configuration required |
|
||||
| `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/) |
|
||||
| `PUSHBULLET` | PushBullet API key | Generate at https://www.pushbullet.com/#settings/account | |
|
||||
| `PUSHOVER_TOKEN` | Pushover access token | Generate at https://pushover.net/apps/build | |
|
||||
| `PUSHOVER_USER` | Pushover username | |
|
||||
| `PAGE_SLEEP_MIN` | Minimum sleep time between queries of the same store | In milliseconds, default: `5000` |
|
||||
| `PAGE_SLEEP_MAX` | Maximum sleep time between queries of the same store | In milliseconds, default: `10000` |
|
||||
| `SCREENSHOT` | Capture screenshot of page if a card is found | Default: `true` |
|
||||
| `SHOW_ONLY_BRANDS` | Filter to show specified brands | Comma separated, e.g.: `evga,zotac` |
|
||||
| `SHOW_ONLY_MODELS` | Filter to show specified models | Comma separated, e.g.: `founders edition,rog strix` |
|
||||
| `SHOW_ONLY_SERIES` | Filter to show specified series | Comma separated, e.g.: `3080` |
|
||||
| `SLACK_CHANNEL` | Slack channel for posting | E.g.: `update`, no need for `#` |
|
||||
| `SLACK_TOKEN` | Slack API token | |
|
||||
| `STORES` | [Supported stores](#supported-stores) you want to be scraped | Comma separated, default: `nvidia` |
|
||||
| `COUNTRY` | [Supported country](#supported-countries) you want to be scraped | Currently only used by Nvidia, default: `usa` |
|
||||
| `SCREENSHOT` | Capture screenshot of page if a card is found | Default: `true` |
|
||||
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token | |
|
||||
| `TELEGRAM_CHAT_ID` | Telegram chat ID | |
|
||||
| `USER_AGENT` | Custom User-Agent header for HTTP requests | Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` |
|
||||
| `TWITTER_CONSUMER_KEY` | Twitter Consumer Key | Generate all Twitter keys at: https://developer.twitter.com/ |
|
||||
| `TWITTER_CONSUMER_SECRET` | Twitter Consumer Secret | |
|
||||
| `TWITTER_ACCESS_TOKEN_KEY` | Twitter Token Key | |
|
||||
| `TWITTER_ACCESS_TOKEN_SECRET` | Twitter Token Secret | |
|
||||
| `TWITTER_TWEET_TAGS` | Optional list of hashtags to append to the tweet message | E.g.: `#nvidia #nvidiastock` |
|
||||
|
||||
> :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.
|
||||
|
||||
> :point_right: Free sounds available [here](https://freesound.org/home/). Place sounds into `resources/sounds/`
|
||||
> :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: You can test your notification configuration by running `npm run test:notification`.
|
||||
|
||||
#### Supported stores
|
||||
|
||||
| **Stores** | **Environment variable** |
|
||||
|:---:|:---:|
|
||||
| Best Buy | `bestbuy`|
|
||||
| Amazon.ca | `amazon-ca`|
|
||||
| Adorama | `adorama`|
|
||||
| Amazon | `amazon`|
|
||||
| Amazon (CA) | `amazon-ca`|
|
||||
| Amazon (DE) | `amazon-de`|
|
||||
| ASUS | `asus` |
|
||||
| B&H | `bandh`|
|
||||
| Best Buy | `bestbuy`|
|
||||
| Best Buy (CA) | `bestbuy-ca`|
|
||||
| EVGA | `evga`|
|
||||
| EVGA (EU) | `evga-eu`|
|
||||
| Micro Center | `microcenter`|
|
||||
| Newegg | `newegg`|
|
||||
| Newegg (CA) | `newegg-ca`|
|
||||
| Nvidia | `nvidia`|
|
||||
| Nvidia (API) | `nvidia-api`|
|
||||
| Office Depot | `officedepot`|
|
||||
| Zotac | `zotac`|
|
||||
|
||||
#### Supported carriers
|
||||
|
||||
| **Carrier** | **Environment variable** | **Notes** |
|
||||
|:---:|:---:|:---:|
|
||||
| AT&T | `att`| |
|
||||
| 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`| |
|
||||
|
||||
#### Supported countries
|
||||
|
||||
| **Country** | **Nvidia.com (3080 FE)** | **Nvidia.com (3090 FE)** | **Notes** |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| austria | `✔` | | |
|
||||
| belgium | `✔` | | Nvidia supports debug |
|
||||
| canada | `✔` | | |
|
||||
| czechia | `✔` | | |
|
||||
| denmark | `✔` | | |
|
||||
| finland | `✔` | | |
|
||||
| france | `✔` | | |
|
||||
| germany | `✔` | | |
|
||||
| great_britain | `✔` | | |
|
||||
| ireland | `✔` | | |
|
||||
| italy | `✔` | | |
|
||||
| luxembourg | `✔` | | Nvidia supports debug |
|
||||
| netherlands | `✔` | | Nvidia supports debug |
|
||||
| poland | `✔` | | |
|
||||
| portugal | `✔` | | |
|
||||
| russia | | | Missing all IDs |
|
||||
| spain | `✔` | | |
|
||||
| sweden | `✔` | | |
|
||||
| usa | `✔` | | Nvidia supports debug |
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -120,6 +184,10 @@ Here is a list of variables that you can use to customize your newly copied `.en
|
||||
|
||||
**Q: I got a problem and need help!** File an [issue](https://github.com/jef/nvidia-snatcher/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/nvidia-snatcher/wiki/Troubleshoot:-General:-Getting-the-latest-code)
|
||||
|
||||
**Q: Why don't my notifications work?** There are probably an [issue](https://github.com/jef/nvidia-snatcher/issues?q=is%3Aissue+sort%3Aupdated-desc+sound+is%3Aclosed) [that] has [already](https://github.com/jef/nvidia-snatcher/issues/182) [been](https://github.com/jef/nvidia-snatcher/issues/116) [resolved](https://github.com/jef/nvidia-snatcher/issues/155)
|
||||
|
||||
**Q: I'd love to contribute, how do I do that?** Make a [pull request](https://github.com/jef/nvidia-snatcher/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc)! All contributions are welcome.
|
||||
|
||||
**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/nvidia-snatcher/issues/11).
|
||||
@@ -131,7 +199,6 @@ Thanks to the great contributors that make this project possible
|
||||
Special shout to initial developers:
|
||||
|
||||
- [@andirew](https://github.com/andirew)
|
||||
- [@davidlbowman](https://github.com/davidlbowman)
|
||||
- [@fuckingrobot](https://github.com/fuckingrobot)
|
||||
- [@ioncaza](https://github.com/IonCaza)
|
||||
- [@malbert69](https://github.com/malbert69)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
Generated
+1157
-157
File diff suppressed because it is too large
Load Diff
+30
-5
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "nvidia-snatcher",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"description": "🔮 For all your Nvidia needs",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "rimraf ./build && tsc",
|
||||
"lint": "xo",
|
||||
"lint:fix": "xo --fix",
|
||||
"start": "npm run build && node build/index.js"
|
||||
"start": "npm run build && node build/index.js",
|
||||
"test:notification": "npm run build && node build/__test__/notification-test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -21,22 +22,46 @@
|
||||
},
|
||||
"homepage": "https://github.com/jef/nvidia-snatcher#readme",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"messaging-api-telegram": "^1.0.0",
|
||||
"messaging-api-telegram": "^1.0.1",
|
||||
"node-notifier": "^8.0.0",
|
||||
"nodemailer": "^6.4.11",
|
||||
"open": "^7.2.1",
|
||||
"puppeteer": "^5.3.0",
|
||||
"puppeteer": "^5.3.1",
|
||||
"puppeteer-extra": "^3.1.15",
|
||||
"puppeteer-extra-plugin-adblocker": "^2.11.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.6.1",
|
||||
"pushbullet": "^2.4.0",
|
||||
"pushover-notifications": "^1.2.2",
|
||||
"twitter": "^1.7.1",
|
||||
"winston": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@slack/web-api": "^5.12.0",
|
||||
"@types/node": "^14.11.1",
|
||||
"@types/async": "^3.2.3",
|
||||
"@types/node": "^14.11.2",
|
||||
"@types/node-notifier": "^8.0.0",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/puppeteer": "^3.0.2",
|
||||
"@types/twitter": "^1.7.0",
|
||||
"discord-webhook-node": "^1.1.8",
|
||||
"husky": "^4.3.0",
|
||||
"play-sound": "^1.1.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.0.2",
|
||||
"xo": "^0.33.1"
|
||||
},
|
||||
"xo": {
|
||||
"rules": {
|
||||
"sort-imports": "error",
|
||||
"sort-keys": "error",
|
||||
"sort-vars": "error"
|
||||
}
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {sendNotification} from '../notification';
|
||||
|
||||
const link: Link = {
|
||||
brand: 'test:brand',
|
||||
cartUrl: 'https://www.example.com/cartUrl',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.example.com/url'
|
||||
};
|
||||
|
||||
const store: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: 'test:container',
|
||||
text: ['test:text']
|
||||
}
|
||||
},
|
||||
links: [link],
|
||||
name: 'test:name'
|
||||
};
|
||||
|
||||
/**
|
||||
* Send test email.
|
||||
*/
|
||||
sendNotification(link, store);
|
||||
@@ -0,0 +1,11 @@
|
||||
import {Page} from 'puppeteer';
|
||||
import {PuppeteerExtraPluginAdblocker} from 'puppeteer-extra-plugin-adblocker';
|
||||
|
||||
export const adBlocker = new PuppeteerExtraPluginAdblocker({
|
||||
blockTrackers: true
|
||||
});
|
||||
|
||||
export async function disableBlockerInPage(page: Page) {
|
||||
const blockerObject = await adBlocker.getBlocker();
|
||||
await blockerObject.disableBlockingInPage(page);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
export const banner = chalk.green.bold(`
|
||||
$$\\ $$\\ $$\\ $$\\ $$\\
|
||||
\\__| $$ |\\__| $$ | $$ |
|
||||
$$$$$$$\\ $$\\ $$\\ $$\\ $$$$$$$ |$$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\
|
||||
$$ __$$\\\\$$\\ $$ |$$ |$$ __$$ |$$ | \\____$$\\ $$$$$$\\ $$ _____|$$ __$$\\ \\____$$\\\\_$$ _| $$ _____|$$ __$$\\ $$ __$$\\ $$ __$$\\
|
||||
$$ | $$ |\\$$\\$$ / $$ |$$ / $$ |$$ | $$$$$$$ |\\______|\\$$$$$$\\ $$ | $$ | $$$$$$$ | $$ | $$ / $$ | $$ |$$$$$$$$ |$$ | \\__|
|
||||
$$ | $$ | \\$$$ / $$ |$$ | $$ |$$ |$$ __$$ | \\____$$\\ $$ | $$ |$$ __$$ | $$ |$$\\ $$ | $$ | $$ |$$ ____|$$ |
|
||||
$$ | $$ | \\$ / $$ |\\$$$$$$$ |$$ |\\$$$$$$$ | $$$$$$$ |$$ | $$ |\\$$$$$$$ | \\$$$$ |\\$$$$$$$\\ $$ | $$ |\\$$$$$$$\\ $$ |
|
||||
\\__| \\__| \\_/ \\__| \\_______|\\__| \\_______| \\_______/ \\__| \\__| \\_______| \\____/ \\_______|\\__| \\__| \\_______|\\__|
|
||||
`);
|
||||
+103
-28
@@ -1,60 +1,135 @@
|
||||
import {resolve} from 'path';
|
||||
import {config} from 'dotenv';
|
||||
import {banner} from './banner';
|
||||
console.log(banner);
|
||||
|
||||
config({path: resolve(__dirname, '../.env')});
|
||||
import {config} from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
config({path: path.resolve(__dirname, '../.env')});
|
||||
|
||||
/**
|
||||
* Returns environment variable, given array, or default array.
|
||||
*
|
||||
* @param environment Interested environment variable.
|
||||
* @param array Default array. If not set, is `[]`.
|
||||
*/
|
||||
function envOrArray(environment: string | undefined, array?: string[]): string[] {
|
||||
return environment ? environment.split(',') : (array ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns environment variable, given boolean, or default boolean.
|
||||
*
|
||||
* @param environment Interested environment variable.
|
||||
* @param boolean Default boolean. If not set, is `true`.
|
||||
*/
|
||||
function envOrBoolean(environment: string | undefined, boolean?: boolean): boolean {
|
||||
return environment ? environment === 'true' : (boolean ?? true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns environment variable, given string, or default string.
|
||||
*
|
||||
* @param environment Interested environment variable.
|
||||
* @param string Default string. If not set, is `''`.
|
||||
*/
|
||||
function envOrString(environment: string | undefined, string?: string): string {
|
||||
return environment ? environment : (string ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns environment variable, given number, or default number.
|
||||
*
|
||||
* @param environment Interested environment variable.
|
||||
* @param number Default number. If not set, is `0`.
|
||||
*/
|
||||
function envOrNumber(environment: string | undefined, number?: number): number {
|
||||
return Number(environment ?? (number ?? 0));
|
||||
}
|
||||
|
||||
const browser = {
|
||||
isHeadless: envOrBoolean(process.env.HEADLESS),
|
||||
isTrusted: envOrBoolean(process.env.BROWSER_TRUSTED, false),
|
||||
maxSleep: envOrNumber(process.env.PAGE_SLEEP_MAX, 10000),
|
||||
minSleep: envOrNumber(process.env.PAGE_SLEEP_MIN, 5000),
|
||||
open: envOrBoolean(process.env.OPEN_BROWSER)
|
||||
};
|
||||
|
||||
const logLevel = process.env.LOG_LEVEL ?? 'info';
|
||||
|
||||
const notifications = {
|
||||
desktop: process.env.DESKTOP_NOTIFICATIONS === 'true',
|
||||
discord: {
|
||||
notifyGroup: envOrArray(process.env.DISCORD_NOTIFY_GROUP),
|
||||
webHookUrl: envOrArray(process.env.DISCORD_WEB_HOOK)
|
||||
},
|
||||
email: {
|
||||
username: process.env.EMAIL_USERNAME ?? '',
|
||||
password: process.env.EMAIL_PASSWORD ?? ''
|
||||
password: envOrString(process.env.EMAIL_PASSWORD),
|
||||
username: envOrString(process.env.EMAIL_USERNAME)
|
||||
},
|
||||
phone: {
|
||||
availableCarriers: new Map([
|
||||
['att', 'txt.att.net'],
|
||||
['bell', 'txt.bell.ca'],
|
||||
['fido', 'fido.ca'],
|
||||
['google', 'msg.fi.google.com'],
|
||||
['koodo', 'msg.koodomobile.com'],
|
||||
['mint', 'mailmymobile.net'],
|
||||
['rogers', 'pcs.rogers.com'],
|
||||
['sprint', 'messaging.sprintpcs.com'],
|
||||
['telus', 'msg.telus.com'],
|
||||
['tmobile', 'tmomail.net'],
|
||||
['verizon', 'vtext.com']
|
||||
['verizon', 'vtext.com'],
|
||||
['virgin', 'vmobl.com'],
|
||||
['virgin-ca', 'vmobile.ca']
|
||||
]),
|
||||
carrier: process.env.PHONE_CARRIER ?? '',
|
||||
number: process.env.PHONE_NUMBER ?? ''
|
||||
carrier: envOrString(process.env.PHONE_CARRIER),
|
||||
number: envOrString(process.env.PHONE_NUMBER)
|
||||
},
|
||||
playSound: process.env.PLAY_SOUND ?? 'false',
|
||||
playSound: envOrString(process.env.PLAY_SOUND),
|
||||
pushBulletApiKey: envOrString(process.env.PUSHBULLET),
|
||||
pushover: {
|
||||
token: process.env.PUSHOVER_TOKEN,
|
||||
user: process.env.PUSHOVER_USER
|
||||
token: envOrString(process.env.PUSHOVER_TOKEN),
|
||||
username: envOrString(process.env.PUSHOVER_USER)
|
||||
},
|
||||
slack: {
|
||||
channel: process.env.SLACK_CHANNEL ?? '',
|
||||
token: process.env.SLACK_TOKEN ?? ''
|
||||
channel: envOrString(process.env.SLACK_CHANNEL),
|
||||
token: envOrString(process.env.SLACK_TOKEN)
|
||||
},
|
||||
telegram: {
|
||||
accessToken: process.env.TELEGRAM_ACCESS_TOKEN ?? '',
|
||||
chatId: process.env.TELEGRAM_CHAT_ID ?? ''
|
||||
accessToken: envOrString(process.env.TELEGRAM_ACCESS_TOKEN),
|
||||
chatId: envOrString(process.env.TELEGRAM_CHAT_ID)
|
||||
},
|
||||
test: process.env.NOTIFICATION_TEST ?? 'false'
|
||||
twitter: {
|
||||
accessTokenKey: envOrString(process.env.TWITTER_ACCESS_TOKEN_KEY),
|
||||
accessTokenSecret: envOrString(process.env.TWITTER_ACCESS_TOKEN_SECRET),
|
||||
consumerKey: envOrString(process.env.TWITTER_CONSUMER_KEY),
|
||||
consumerSecret: envOrString(process.env.TWITTER_CONSUMER_SECRET),
|
||||
tweetTags: envOrString(process.env.TWITTER_TWEET_TAGS)
|
||||
}
|
||||
};
|
||||
|
||||
const page = {
|
||||
capture: process.env.SCREENSHOT ?? 'true',
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
navigationTimeout: Number(process.env.PAGE_TIMEOUT) ?? 30000,
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
|
||||
inStockWaitTime: envOrNumber(process.env.IN_STOCK_WAIT_TIME),
|
||||
navigationTimeout: envOrNumber(process.env.PAGE_TIMEOUT, 30000),
|
||||
screenshot: envOrBoolean(process.env.SCREENSHOT),
|
||||
userAgent: envOrString(process.env.USER_AGENT, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'),
|
||||
width: 1920
|
||||
};
|
||||
|
||||
const openBrowser = process.env.OPEN_BROWSER ?? 'true';
|
||||
const rateLimitTimeout = Number(process.env.RATE_LIMIT_TIMEOUT) ?? 5000;
|
||||
const stores = process.env.STORES ? process.env.STORES.split(',') : ['nvidia'];
|
||||
const showOnlyBrands = process.env.SHOW_ONLY_BRANDS ? process.env.SHOW_ONLY_BRANDS.split(',') : [];
|
||||
const store = {
|
||||
country: envOrString(process.env.COUNTRY, 'usa'),
|
||||
microCenterLocation: envOrString(process.env.MICROCENTER_LOCATION, 'web'),
|
||||
showOnlyBrands: envOrArray(process.env.SHOW_ONLY_BRANDS),
|
||||
showOnlyModels: envOrArray(process.env.SHOW_ONLY_MODELS),
|
||||
showOnlySeries: envOrArray(process.env.SHOW_ONLY_SERIES, ['3070', '3080', '3090']),
|
||||
stores: envOrArray(process.env.STORES, ['nvidia'])
|
||||
};
|
||||
|
||||
export const Config = {
|
||||
browser,
|
||||
logLevel,
|
||||
notifications,
|
||||
rateLimitTimeout,
|
||||
page,
|
||||
stores,
|
||||
openBrowser,
|
||||
showOnlyBrands
|
||||
store
|
||||
};
|
||||
|
||||
+37
-22
@@ -1,34 +1,50 @@
|
||||
import {Config} from './config';
|
||||
import {Stores} from './store/model';
|
||||
import {Logger} from './logger';
|
||||
import {sendNotification} from './notification';
|
||||
import {lookup} from './store';
|
||||
import puppeteer from 'puppeteer';
|
||||
import {Stores} from './store/model';
|
||||
import {adBlocker} from './adblocker';
|
||||
import {getSleepTime} from './util';
|
||||
import puppeteer from 'puppeteer-extra';
|
||||
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||
import {tryLookupAndLoop} from './store';
|
||||
|
||||
puppeteer.use(stealthPlugin());
|
||||
puppeteer.use(adBlocker);
|
||||
|
||||
/**
|
||||
* Starts the bot.
|
||||
*/
|
||||
async function main() {
|
||||
const results = [];
|
||||
const browser = await puppeteer.launch();
|
||||
if (Stores.length === 0) {
|
||||
Logger.error('✖ no stores selected', Stores);
|
||||
return;
|
||||
}
|
||||
|
||||
const args: string[] = [];
|
||||
|
||||
// Skip Chromium Linux Sandbox
|
||||
// https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#setting-up-chrome-linux-sandbox
|
||||
if (Config.browser.isTrusted) {
|
||||
args.push('--no-sandbox');
|
||||
args.push('--disable-setuid-sandbox');
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
args,
|
||||
defaultViewport: {
|
||||
height: Config.page.height,
|
||||
width: Config.page.width
|
||||
},
|
||||
headless: Config.browser.isHeadless
|
||||
});
|
||||
|
||||
for (const store of Stores) {
|
||||
Logger.debug(store.links);
|
||||
results.push(lookup(browser, store));
|
||||
if (store.setupAction !== undefined) {
|
||||
store.setupAction(browser);
|
||||
}
|
||||
|
||||
setTimeout(tryLookupAndLoop, getSleepTime(), browser, store);
|
||||
}
|
||||
|
||||
await Promise.all(results);
|
||||
await browser.close();
|
||||
|
||||
Logger.info('↗ trying stores again');
|
||||
setTimeout(main, Config.rateLimitTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email.
|
||||
*/
|
||||
if (Config.notifications.test === 'true') {
|
||||
sendNotification('test');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +53,6 @@ if (Config.notifications.test === 'true') {
|
||||
try {
|
||||
void main();
|
||||
} catch (error) {
|
||||
// Ignoring errors; more than likely due to rate limits
|
||||
Logger.error(error);
|
||||
Logger.error('✖ something bad happened, resetting nvidia-snatcher', error);
|
||||
void main();
|
||||
}
|
||||
|
||||
+45
-5
@@ -1,4 +1,7 @@
|
||||
import {Link, Store} from './store/model';
|
||||
import winston, {format} from 'winston';
|
||||
import {Config} from './config';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const prettyJson = format.printf(info => {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
@@ -7,11 +10,10 @@ const prettyJson = format.printf(info => {
|
||||
info.message = JSON.stringify(info.message, null, 4);
|
||||
}
|
||||
|
||||
return `[${timestamp}] ${info.level} :: ${info.message}`;
|
||||
return chalk.grey(`[${timestamp}]`) + ` ${info.level} ` + chalk.grey('::') + ` ${info.message}`;
|
||||
});
|
||||
|
||||
export const Logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL ?? 'info',
|
||||
format: format.combine(
|
||||
format.colorize(),
|
||||
format.prettyPrint(),
|
||||
@@ -19,7 +21,45 @@ export const Logger = winston.createLogger({
|
||||
format.simple(),
|
||||
prettyJson
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console({})
|
||||
]
|
||||
level: Config.logLevel,
|
||||
transports: [new winston.transports.Console({})]
|
||||
});
|
||||
|
||||
export const Print = {
|
||||
captcha(link: Link, store: Store, color?: boolean): string {
|
||||
if (color) {
|
||||
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow('CAPTCHA');
|
||||
}
|
||||
|
||||
return `✖ ${buildProductString(link, store)} :: CAPTCHA`;
|
||||
},
|
||||
inStock(link: Link, store: Store, color?: boolean): string {
|
||||
if (color) {
|
||||
return chalk.green.bold(`🚀🚨 ${buildProductString(link, store, true)} :: IN STOCK 🚨🚀`);
|
||||
}
|
||||
|
||||
return `🚀🚨 ${buildProductString(link, store)} :: IN STOCK 🚨🚀`;
|
||||
},
|
||||
outOfStock(link: Link, store: Store, color?: boolean): string {
|
||||
if (color) {
|
||||
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.red('OUT OF STOCK');
|
||||
}
|
||||
|
||||
return `✖ ${buildProductString(link, store)} :: OUT OF STOCK`;
|
||||
},
|
||||
rateLimit(link: Link, store: Store, color?: boolean): string {
|
||||
if (color) {
|
||||
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow('RATE LIMIT EXCEEDED');
|
||||
}
|
||||
|
||||
return `✖ ${buildProductString(link, store)} :: RATE LIMIT EXCEEDED`;
|
||||
}
|
||||
};
|
||||
|
||||
function buildProductString(link: Link, store: Store, color?: boolean): string {
|
||||
if (color) {
|
||||
return chalk.cyan(`[${store.name}]`) + chalk.grey(` [${link.brand} (${link.series})] ${link.model}`);
|
||||
}
|
||||
|
||||
return `[${store.name}] [${link.brand} (${link.series})] ${link.model}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import notifier from 'node-notifier';
|
||||
|
||||
export function sendDesktopNotification(link: Link, store: Store) {
|
||||
(async () => {
|
||||
notifier.notify({
|
||||
message: link.cartUrl ? link.cartUrl : link.url,
|
||||
title: Print.inStock(link, store)
|
||||
});
|
||||
|
||||
Logger.info('✔ desktop notification sent');
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {MessageBuilder, Webhook} from 'discord-webhook-node';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
|
||||
const hooks = Config.notifications.discord.webHookUrl;
|
||||
const notifyGroup = Config.notifications.discord.notifyGroup;
|
||||
|
||||
export function sendDiscordMessage(link: Link, store: Store) {
|
||||
(async () => {
|
||||
try {
|
||||
const embed = new MessageBuilder();
|
||||
embed.setTitle('Stock Notification');
|
||||
embed.addField('URL', link.cartUrl ? link.cartUrl : link.url, true);
|
||||
embed.addField('Store', store.name, true);
|
||||
embed.addField('Brand', link.brand, true);
|
||||
embed.addField('Model', link.model, true);
|
||||
|
||||
if (notifyGroup) {
|
||||
embed.setText(notifyGroup.join(' '));
|
||||
}
|
||||
|
||||
embed.setColor(0x76B900);
|
||||
embed.setTimestamp();
|
||||
|
||||
const promises = [];
|
||||
for (const hook of hooks) {
|
||||
promises.push(new Webhook(hook).send(embed));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
Logger.info('✔ discord message sent');
|
||||
} catch (error) {
|
||||
Logger.error('✖ couldn\'t send discord message', error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
+24
-20
@@ -1,34 +1,38 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import Mail from 'nodemailer/lib/mailer';
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import Mail from 'nodemailer/lib/mailer';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
const email = Config.notifications.email;
|
||||
const subject = 'NVIDIA - BUY NOW';
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: email.username,
|
||||
pass: email.password
|
||||
}
|
||||
pass: email.password,
|
||||
user: email.username
|
||||
},
|
||||
service: 'gmail'
|
||||
});
|
||||
|
||||
const mailOptions: Mail.Options = {
|
||||
from: email.username,
|
||||
to: email.username,
|
||||
subject
|
||||
};
|
||||
export function sendEmail(link: Link, store: Store) {
|
||||
const mailOptions: Mail.Options = {
|
||||
attachments: link.screenshot ? [
|
||||
{
|
||||
filename: link.screenshot,
|
||||
path: `./${link.screenshot}`
|
||||
}
|
||||
] : undefined,
|
||||
from: email.username,
|
||||
subject: Print.inStock(link, store),
|
||||
text: link.cartUrl ? link.cartUrl : link.url,
|
||||
to: email.username
|
||||
};
|
||||
|
||||
export function sendEmail(text: string) {
|
||||
mailOptions.text = text;
|
||||
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
transporter.sendMail(mailOptions, error => {
|
||||
if (error) {
|
||||
Logger.error(error);
|
||||
Logger.error('✖ couldn\'t send email', error);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.info(`✔ email sent: ${info.response}`);
|
||||
Logger.info('✔ email sent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,38 +1,75 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Config} from '../config';
|
||||
import {sendEmail} from './email';
|
||||
import {sendSMS} from './sms';
|
||||
import {Logger} from '../logger';
|
||||
import {playSound} from './sound';
|
||||
import {sendSlackMessage} from './slack';
|
||||
import {sendDesktopNotification} from './desktop';
|
||||
import {sendDiscordMessage} from './discord';
|
||||
import {sendEmail} from './email';
|
||||
import {sendPushBulletNotification} from './pushbullet';
|
||||
import {sendPushoverNotification} from './pushover';
|
||||
import {sendSMS} from './sms';
|
||||
import {sendSlackMessage} from './slack';
|
||||
import {sendTelegramMessage} from './telegram';
|
||||
import {sendTweet} from './twitter';
|
||||
|
||||
const notifications = Config.notifications;
|
||||
|
||||
export function sendNotification(cartUrl: string) {
|
||||
export function sendNotification(link: Link, store: Store) {
|
||||
if (notifications.email.username && notifications.email.password) {
|
||||
sendEmail(cartUrl);
|
||||
}
|
||||
|
||||
if (notifications.slack.channel && notifications.slack.token) {
|
||||
sendSlackMessage(cartUrl);
|
||||
}
|
||||
|
||||
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
|
||||
sendTelegramMessage(cartUrl);
|
||||
Logger.debug('↗ sending email');
|
||||
sendEmail(link, store);
|
||||
}
|
||||
|
||||
if (notifications.phone.number) {
|
||||
const carrier = notifications.phone.carrier.toLowerCase();
|
||||
Logger.debug('↗ sending sms');
|
||||
const carrier = notifications.phone.carrier;
|
||||
if (carrier && notifications.phone.availableCarriers.has(carrier)) {
|
||||
sendSMS(cartUrl);
|
||||
sendSMS(link, store);
|
||||
}
|
||||
}
|
||||
|
||||
if (notifications.pushover.token && notifications.pushover.user) {
|
||||
sendPushoverNotification(cartUrl);
|
||||
}
|
||||
|
||||
if (notifications.playSound === 'true') {
|
||||
if (notifications.playSound) {
|
||||
Logger.debug('↗ playing sound');
|
||||
playSound();
|
||||
}
|
||||
|
||||
if (notifications.desktop) {
|
||||
Logger.debug('↗ sending desktop notification');
|
||||
sendDesktopNotification(link, store);
|
||||
}
|
||||
|
||||
if (notifications.discord.webHookUrl) {
|
||||
Logger.debug('↗ sending discord message');
|
||||
sendDiscordMessage(link, store);
|
||||
}
|
||||
|
||||
if (notifications.slack.channel && notifications.slack.token) {
|
||||
Logger.debug('↗ sending slack message');
|
||||
sendSlackMessage(link, store);
|
||||
}
|
||||
|
||||
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
|
||||
Logger.debug('↗ sending telegram message');
|
||||
sendTelegramMessage(link, store);
|
||||
}
|
||||
|
||||
if (notifications.pushBulletApiKey) {
|
||||
Logger.debug('↗ sending pushbullet message');
|
||||
sendPushBulletNotification(link, store);
|
||||
}
|
||||
|
||||
if (notifications.pushover.token && notifications.pushover.username) {
|
||||
Logger.debug('↗ sending pushover message');
|
||||
sendPushoverNotification(link, store);
|
||||
}
|
||||
|
||||
if (
|
||||
notifications.twitter.accessTokenKey &&
|
||||
notifications.twitter.accessTokenSecret &&
|
||||
notifications.twitter.consumerKey &&
|
||||
notifications.twitter.consumerSecret
|
||||
) {
|
||||
Logger.debug('↗ sending twitter message');
|
||||
sendTweet(link, store);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import PushBullet from 'pushbullet';
|
||||
|
||||
const pushBulletApiKey = Config.notifications.pushBulletApiKey;
|
||||
|
||||
export function sendPushBulletNotification(link: Link, store: Store) {
|
||||
const pusher = new PushBullet(pushBulletApiKey);
|
||||
|
||||
pusher.note(
|
||||
{},
|
||||
Print.inStock(link, store),
|
||||
link.cartUrl ? link.cartUrl : link.url,
|
||||
(error: Error) => {
|
||||
if (error) {
|
||||
Logger.error('✖ couldn\'t send pushbullet message', error);
|
||||
} else {
|
||||
Logger.info('✔ pushbullet message sent');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
import Push from 'pushover-notifications';
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import Push from 'pushover-notifications';
|
||||
|
||||
const pushover = Config.notifications.pushover;
|
||||
const push = new Push({
|
||||
user: pushover.user,
|
||||
token: pushover.token
|
||||
token: pushover.token,
|
||||
user: pushover.username
|
||||
});
|
||||
|
||||
export function sendPushoverNotification(text: string) {
|
||||
export function sendPushoverNotification(link: Link, store: Store) {
|
||||
const message = {
|
||||
message: text
|
||||
message: link.cartUrl ? link.cartUrl : link.url,
|
||||
title: Print.inStock(link, store)
|
||||
};
|
||||
|
||||
push.send(message, (err: Error, result: string) => {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
push.send(message, (error: Error) => {
|
||||
if (error) {
|
||||
Logger.error('✖ couldn\'t send pushover message', error);
|
||||
} else {
|
||||
Logger.info(`✔ Pushover notification sent: ${result}`);
|
||||
Logger.info('✔ pushover message sent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import {WebClient} from '@slack/web-api';
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import {WebClient} from '@slack/web-api';
|
||||
|
||||
const channel = Config.notifications.slack.channel;
|
||||
const token = Config.notifications.slack.token;
|
||||
const web = new WebClient(token);
|
||||
|
||||
export function sendSlackMessage(text: string) {
|
||||
export function sendSlackMessage(link: Link, store: Store) {
|
||||
(async () => {
|
||||
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||
|
||||
try {
|
||||
const result = await web.chat.postMessage({text, channel});
|
||||
const result = await web.chat.postMessage({
|
||||
channel,
|
||||
text: `${Print.inStock(link, store)}\n${givenUrl}`
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
Logger.error(result.error);
|
||||
Logger.error('✖ couldn\'t send slack message', result);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info(`✔ slack message sent to '${channel}': ${text}`);
|
||||
Logger.info('✔ slack message sent');
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error('✖ couldn\'t send slack message', error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
+32
-21
@@ -1,41 +1,52 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import Mail from 'nodemailer/lib/mailer';
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import Mail from 'nodemailer/lib/mailer';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
if (Config.notifications.phone.number && !Config.notifications.email.username) {
|
||||
Logger.warn('✖ in order to recieve sms alerts, email notifications must also be configured');
|
||||
}
|
||||
|
||||
const subject = 'NVIDIA - BUY NOW';
|
||||
const [email, phone] = [Config.notifications.email, Config.notifications.phone];
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: email.username,
|
||||
pass: email.password
|
||||
}
|
||||
pass: email.password,
|
||||
user: email.username
|
||||
},
|
||||
service: 'gmail'
|
||||
});
|
||||
|
||||
const mailOptions: Mail.Options = {
|
||||
from: Config.notifications.email.username,
|
||||
to: generateAddress(),
|
||||
subject
|
||||
};
|
||||
export function sendSMS(link: Link, store: Store) {
|
||||
const mailOptions: Mail.Options = {
|
||||
attachments: link.screenshot ? [
|
||||
{
|
||||
filename: link.screenshot,
|
||||
path: `./${link.screenshot}`
|
||||
}
|
||||
] : undefined,
|
||||
from: email.username,
|
||||
subject: Print.inStock(link, store),
|
||||
text: link.cartUrl ? link.cartUrl : link.url,
|
||||
to: generateAddress()
|
||||
};
|
||||
|
||||
export function sendSMS(text: string) {
|
||||
mailOptions.text = text;
|
||||
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
transporter.sendMail(mailOptions, error => {
|
||||
if (error) {
|
||||
Logger.error(error);
|
||||
Logger.error('✖ couldn\'t send sms', error);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.info(`✔ sms sent: ${info.response}`);
|
||||
Logger.info('✔ sms sent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateAddress() {
|
||||
const carrier = phone.carrier.toLowerCase();
|
||||
const carrier = phone.carrier;
|
||||
|
||||
if (carrier && phone.availableCarriers.has(carrier)) {
|
||||
return [phone.number, phone.availableCarriers.get(carrier)].join('@');
|
||||
}
|
||||
|
||||
Logger.error('✖ unknown carrier', carrier);
|
||||
}
|
||||
|
||||
+28
-17
@@ -1,25 +1,36 @@
|
||||
import playerLib = require('play-sound');
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
import playerLib from 'play-sound';
|
||||
|
||||
const notificationSound = './resources/sounds/' + Config.notifications.playSound;
|
||||
const player = playerLib();
|
||||
let player: any;
|
||||
|
||||
if (Config.notifications.playSound) {
|
||||
player = playerLib();
|
||||
|
||||
if (player.player === null) {
|
||||
Logger.warn('✖ couldn\'t find sound player');
|
||||
} else {
|
||||
const playerName: string = player.player;
|
||||
Logger.info(`✔ sound player found: ${playerName}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function playSound() {
|
||||
// Check if file exists
|
||||
fs.access(notificationSound, fs.constants.F_OK, err => {
|
||||
if (err) {
|
||||
Logger.error(`error opening sound file: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
player.play(notificationSound, (err: string) => {
|
||||
Logger.info('✔ playing sound');
|
||||
|
||||
if (err) {
|
||||
Logger.error(`error playing sound: ${err}`);
|
||||
if (player.player !== null) {
|
||||
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) => {
|
||||
if (error) {
|
||||
Logger.error('✖ couldn\'t play sound', error);
|
||||
}
|
||||
|
||||
Logger.info('✔ played sound');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import {TelegramClient} from 'messaging-api-telegram';
|
||||
|
||||
const telegram = Config.notifications.telegram;
|
||||
@@ -8,13 +9,15 @@ const client = new TelegramClient({
|
||||
accessToken: telegram.accessToken
|
||||
});
|
||||
|
||||
export function sendTelegramMessage(text: string) {
|
||||
export function sendTelegramMessage(link: Link, store: Store) {
|
||||
(async () => {
|
||||
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||
|
||||
try {
|
||||
await client.sendMessage(telegram.chatId, text);
|
||||
Logger.info(`✔ telegram message sent to '${telegram.chatId}': ${text}`);
|
||||
await client.sendMessage(telegram.chatId, `${Print.inStock(link, store)}\n${givenUrl}`);
|
||||
Logger.info('✔ telegram message sent');
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error('✖ couldn\'t send telegram message', error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import {Link, Store} from '../store/model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {Config} from '../config';
|
||||
import Twitter from 'twitter';
|
||||
|
||||
const twitter = Config.notifications.twitter;
|
||||
|
||||
const client = new Twitter({
|
||||
access_token_key: twitter.accessTokenKey,
|
||||
access_token_secret: twitter.accessTokenSecret,
|
||||
consumer_key: twitter.consumerKey,
|
||||
consumer_secret: twitter.consumerSecret
|
||||
});
|
||||
|
||||
export function sendTweet(link: Link, store: Store) {
|
||||
let status = `${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`;
|
||||
|
||||
if (twitter.tweetTags) {
|
||||
status += `\n\n${twitter.tweetTags}`;
|
||||
}
|
||||
|
||||
client.post('statuses/update', {status}, error => {
|
||||
if (error) {
|
||||
Logger.error('✖ couldn\'t send twitter notification', error);
|
||||
} else {
|
||||
Logger.info('✔ twitter notification sent');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import {Config} from '../config';
|
||||
import {Link} from './model';
|
||||
|
||||
/**
|
||||
* Returns true if the brand should be checked for stock
|
||||
*
|
||||
* @param brand The brand of the GPU
|
||||
*/
|
||||
function filterBrand(brand: Link['brand']): boolean {
|
||||
if (Config.store.showOnlyBrands.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Config.store.showOnlyBrands.includes(brand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the model should be checked for stock
|
||||
*
|
||||
* @param model The model of the GPU
|
||||
*/
|
||||
function filterModel(model: Link['model']): boolean {
|
||||
if (Config.store.showOnlyModels.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sanitizedModel = model.replace(/\s/g, '');
|
||||
for (const configModel of Config.store.showOnlyModels) {
|
||||
const sanitizedConfigModel = configModel.replace(/\s/g, '');
|
||||
if (sanitizedModel === sanitizedConfigModel) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the series should be checked for stock
|
||||
*
|
||||
* @param series The series of the GPU
|
||||
*/
|
||||
function filterSeries(series: Link['series']): boolean {
|
||||
if (Config.store.showOnlySeries.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Config.store.showOnlySeries.includes(series);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the link should be checked for stock
|
||||
*
|
||||
* @param link The store link of the GPU
|
||||
*/
|
||||
export function filterStoreLink(link: Link): boolean {
|
||||
return (
|
||||
filterBrand(link.brand) &&
|
||||
filterModel(link.model) &&
|
||||
filterSeries(link.series)
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Checks if DOM has any out-of-stock related text.
|
||||
* Checks if DOM has any related text.
|
||||
*
|
||||
* @param domText Complete DOM of website.
|
||||
* @param oosLabels Out-of-stock labels.
|
||||
* @param searchLabels Search labels for a match.
|
||||
*/
|
||||
export function includesLabels(domText: string, oosLabels: string[]): boolean {
|
||||
export function includesLabels(domText: string, searchLabels: string[]): boolean {
|
||||
const domTextLowerCase = domText.toLowerCase();
|
||||
return oosLabels.some(label => domTextLowerCase.includes(label));
|
||||
return searchLabels.some(label => domTextLowerCase.includes(label));
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './lookup';
|
||||
export * from './includes-labels';
|
||||
|
||||
+110
-61
@@ -1,85 +1,134 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
import {Browser, Page, Response} from 'puppeteer';
|
||||
import {Link, Store} from './model';
|
||||
import {Logger, Print} from '../logger';
|
||||
import {closePage, delay, getSleepTime} from '../util';
|
||||
import {Config} from '../config';
|
||||
import {Logger} from '../logger';
|
||||
import open from 'open';
|
||||
import {Store} from './model';
|
||||
import {sendNotification} from '../notification';
|
||||
import {filterStoreLink} from './filter';
|
||||
import {includesLabels} from './includes-labels';
|
||||
import open from 'open';
|
||||
import {sendNotification} from '../notification';
|
||||
|
||||
/**
|
||||
* Returns true if the brand should be checked for stock
|
||||
*
|
||||
* @param brand The brand of the GPU
|
||||
*/
|
||||
function filterBrand(brand: string) {
|
||||
if (Config.showOnlyBrands.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Config.showOnlyBrands.includes(brand);
|
||||
}
|
||||
const inStock: Record<string, boolean> = {};
|
||||
|
||||
/**
|
||||
* Responsible for looking up information about a each product within
|
||||
* a `Store`. It's important that we ignore `no-await-in-loop` here
|
||||
* because we don't want to get rate limited within the same store.
|
||||
*
|
||||
* @param browser Current browser in use.
|
||||
* @param browser Puppeteer browser.
|
||||
* @param store Vendor of graphics cards.
|
||||
*/
|
||||
export async function lookup(browser: puppeteer.Browser, store: Store) {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
async function lookup(browser: Browser, store: Store) {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (const link of store.links) {
|
||||
if (!filterBrand(link.brand)) {
|
||||
if (!filterStoreLink(link)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const page = await browser.newPage();
|
||||
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
|
||||
await page.setUserAgent(Config.page.userAgent);
|
||||
await page.setViewport({
|
||||
height: Config.page.height,
|
||||
width: Config.page.width
|
||||
});
|
||||
|
||||
const graphicsCard = `${link.brand} ${link.model}`;
|
||||
|
||||
try {
|
||||
await page.goto(link.url, {waitUntil: 'networkidle0'});
|
||||
} catch {
|
||||
Logger.error(`✖ [${store.name}] ${graphicsCard} skipping; timed out`);
|
||||
await page.close();
|
||||
continue;
|
||||
await lookupCard(browser, store, page, link);
|
||||
} catch (error) {
|
||||
Logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`);
|
||||
}
|
||||
|
||||
const bodyHandle = await page.$('body');
|
||||
const textContent = await page.evaluate(body => body.textContent, bodyHandle);
|
||||
|
||||
Logger.debug(textContent);
|
||||
|
||||
if (includesLabels(textContent, link.oosLabels)) {
|
||||
Logger.info(`✖ [${store.name}] still out of stock: ${graphicsCard}`);
|
||||
} else if (link.captchaLabels && includesLabels(textContent, link.captchaLabels)) {
|
||||
Logger.warn(`✖ [${store.name}] CAPTCHA from: ${graphicsCard}`);
|
||||
} else {
|
||||
Logger.info(`🚀🚀🚀 [${store.name}] ${graphicsCard} IN STOCK 🚀🚀🚀`);
|
||||
Logger.info(link.url);
|
||||
|
||||
if (Config.page.capture === 'true') {
|
||||
Logger.debug('ℹ saving screenshot');
|
||||
await page.screenshot({path: `success-${Date.now()}.png`});
|
||||
}
|
||||
|
||||
const givenUrl = store.cartUrl ? store.cartUrl : link.url;
|
||||
|
||||
if (Config.openBrowser === 'true') {
|
||||
await open(givenUrl);
|
||||
}
|
||||
|
||||
sendNotification(givenUrl);
|
||||
}
|
||||
|
||||
await page.close();
|
||||
await closePage(page);
|
||||
}
|
||||
/* eslint-enable no-await-in-loop */
|
||||
/* eslint-enable no-await-in-loop */
|
||||
}
|
||||
|
||||
async function lookupCard(browser: Browser, store: Store, page: Page, link: Link) {
|
||||
const givenWaitFor = store.waitUntil ? store.waitUntil : 'networkidle0';
|
||||
const response: Response | null = await page.goto(link.url, {waitUntil: givenWaitFor});
|
||||
|
||||
if (await lookupCardInStock(store, page)) {
|
||||
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
|
||||
Logger.info(`${Print.inStock(link, store, true)}\n${givenUrl}`);
|
||||
|
||||
if (Config.browser.open) {
|
||||
if (link.openCartAction === undefined) {
|
||||
await open(givenUrl);
|
||||
} else {
|
||||
await link.openCartAction(browser);
|
||||
}
|
||||
}
|
||||
|
||||
sendNotification(link, store);
|
||||
|
||||
if (Config.page.inStockWaitTime) {
|
||||
inStock[store.name] = true;
|
||||
|
||||
setTimeout(() => {
|
||||
inStock[store.name] = false;
|
||||
}, 1000 * Config.page.inStockWaitTime);
|
||||
}
|
||||
|
||||
if (Config.page.screenshot) {
|
||||
Logger.debug('ℹ saving screenshot');
|
||||
|
||||
link.screenshot = `success-${Date.now()}.png`;
|
||||
await page.screenshot({path: link.screenshot});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (await lookupPageHasCaptcha(store, page)) {
|
||||
Logger.warn(Print.captcha(link, store, true));
|
||||
await delay(getSleepTime());
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && response.status() === 429) {
|
||||
Logger.warn(Print.rateLimit(link, store, true));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info(Print.outOfStock(link, store, true));
|
||||
}
|
||||
|
||||
async function lookupCardInStock(store: Store, page: Page) {
|
||||
const stockHandle = await page.$(store.labels.inStock.container);
|
||||
|
||||
const visible = await page.evaluate(element => element && element.offsetWidth > 0 && element.offsetHeight > 0, stockHandle);
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stockContent = await page.evaluate(element => element.outerHTML, stockHandle);
|
||||
|
||||
Logger.debug(stockContent);
|
||||
|
||||
return includesLabels(stockContent, store.labels.inStock.text);
|
||||
}
|
||||
|
||||
async function lookupPageHasCaptcha(store: Store, page: Page) {
|
||||
if (!store.labels.captcha) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const captchaHandle = await page.$(store.labels.captcha.container);
|
||||
const captchaContent = await page.evaluate(element => element.textContent, captchaHandle);
|
||||
|
||||
return includesLabels(captchaContent, store.labels.captcha.text);
|
||||
}
|
||||
|
||||
export async function tryLookupAndLoop(browser: Browser, store: Store) {
|
||||
Logger.debug(`[${store.name}] Starting lookup...`);
|
||||
try {
|
||||
if (Config.page.inStockWaitTime && inStock[store.name]) {
|
||||
Logger.info(`[${store.name}] Has stock, waiting before trying to lookup again...`);
|
||||
} else {
|
||||
await lookup(browser, store);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
}
|
||||
|
||||
const sleepTime = getSleepTime();
|
||||
Logger.debug(`[${store.name}] Lookup done, next one in ${sleepTime} ms`);
|
||||
setTimeout(tryLookupAndLoop, sleepTime, browser, store);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Adorama: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['please verify you are a human']
|
||||
},
|
||||
inStock: {
|
||||
container: '.buy-section.purchase',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.adorama.com/ev08gp43067k.html'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/pnv301tfxmpb.html'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/msig380gxt1.html'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/ev10g53897kr.html'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/ev10g53885kr.html'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/ev10g53895kr.html'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/ev10g53883kr.html'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/ev10g53881kr.html'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/msig38v3x10c.html'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8 rbg',
|
||||
series: '3080',
|
||||
url: 'https://www.adorama.com/png30801tfxb.html'
|
||||
}
|
||||
],
|
||||
name: 'adorama'
|
||||
};
|
||||
@@ -1,83 +1,88 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const AmazonCa: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['enter the characters you see below']
|
||||
},
|
||||
inStock: {
|
||||
container: '#desktop_buybox',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.amazon.ca/dp/B07PBLD2MX'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming trio',
|
||||
url: 'https://www.amazon.ca/MSI-GeForce-RTX-3080-10G/dp/B08HR7SV3M?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR7SV3M'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3gaming',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Technology-Backplate-10G-P5-3895-KR/dp/B08HR3DPGW?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'ftw3',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR3DPGW'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3ultra',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Technology-Backplate-10G-P5-3897-KR/dp/B08HR3Y5GQ?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'ftw3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR3Y5GQ'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3ultra',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Cooling-Backplate-10G-P5-3885-KR/dp/B08HR55YB5?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR55YB5'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3gaming',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Cooling-Backplate-10G-P5-3883-KR/dp/B08HR4RJ3Q?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR4RJ3Q'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3black',
|
||||
url: 'https://www.amazon.ca/EVGA-GeForce-Gaming-Cooling-10G-P5-3881-KR/dp/B08HR6FMF3?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR6FMF3'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'windforce',
|
||||
url: 'https://www.amazon.ca/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080GAMING/dp/B08HJTH61J?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HJTH61J'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'windforce eagle',
|
||||
url: 'https://www.amazon.ca/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080EAGLE/dp/B08HJS2JLJ?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'eagle oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HJS2JLJ'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
url: 'https://www.amazon.ca/Asus-90YV0FB0-M0AM00-TUF-RTX3080-10G-GAMING/dp/B08HHDP9DW?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HHDP9DW'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tufoc',
|
||||
url: 'https://www.amazon.ca/Asus-90YV0FB1-M0AM00-TUF-RTX3080-O10G-GAMING/dp/B08HH5WF97?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'tuf oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HH5WF97'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus',
|
||||
url: 'https://www.amazon.ca/MSI-GeForce-RTX-3080-10G/dp/B08HR5SXPS?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.ca/dp/B08HR5SXPS'
|
||||
}
|
||||
],
|
||||
name: 'amazon-ca'
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const AmazonDe: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['geben sie die unten angezeigten zeichen ein']
|
||||
},
|
||||
inStock: {
|
||||
container: '#desktop_buybox',
|
||||
text: ['in den einkaufswagen']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.amazon.com/dp/B07MQ36Z6L'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HBTJMLJ'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8-rgb',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HBR7QBM'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HM4V2DH'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HGYXP4C'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HJ9XFNM'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HGBYWQ6'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HGLN78Q'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HH1BMQQ'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HLZXHZY'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'eagle oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HHZVZ3N'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HN4DSTC'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HM4M621'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.de/dp/B08HR1NPPQ'
|
||||
}
|
||||
],
|
||||
name: 'amazon-de'
|
||||
};
|
||||
+78
-28
@@ -1,62 +1,112 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Amazon: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['enter the characters you see below']
|
||||
},
|
||||
inStock: {
|
||||
container: '#desktop_buybox',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.amazon.com/dp/B07MQ36Z6L'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8',
|
||||
url: 'https://www.amazon.com/PNY-GeForce-Gaming-Epic-X-Graphics/dp/B08HBR7QBM?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HBR7QBM'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8 rgb',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HBTJMLJ'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming trio',
|
||||
url: 'https://www.amazon.com/MSI-GeForce-RTX-3080-10G/dp/B08HR7SV3M?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR7SV3M'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR3Y5GQ'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR55YB5'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3',
|
||||
url: 'https://www.amazon.com/EVGA-10G-P5-3897-KR-GeForce-Technology-Backplate/dp/B08HR3Y5GQ?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR3DPGW'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3',
|
||||
url: 'https://www.amazon.com/EVGA-10G-P5-3885-KR-GeForce-Cooling-Backplate/dp/B08HR55YB5?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR4RJ3Q'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR6FMF3'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'windforce',
|
||||
url: 'https://www.amazon.com/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080GAMING/dp/B08HJTH61J?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HJTH61J'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'windforce eagle',
|
||||
url: 'https://www.amazon.com/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080EAGLE/dp/B08HJS2JLJ?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'eagle oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HJS2JLJ'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HH5WF97'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
url: 'https://www.amazon.com/ASUS-Graphics-DisplayPort-Military-Grade-Certification/dp/B08HH5WF97?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HHDP9DW'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'strix',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08J6F174Z'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus',
|
||||
url: 'https://www.amazon.com/MSI-GeForce-RTX-3080-10G/dp/B08HR5SXPS?ref_=ast_sto_dp',
|
||||
oosLabels: ['currently unavailable'],
|
||||
captchaLabels: ['enter the characters you see below']
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HR5SXPS'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: 'https://www.amazon.com/dp/B08HJNKT3P'
|
||||
}
|
||||
],
|
||||
name: 'amazon'
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Asus: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '#item_add_cart',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://store.asus.com/us/item/202003AM280000002/'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf oc',
|
||||
series: '3080',
|
||||
url: 'https://store.asus.com/us/item/202009AM160000001/'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3080',
|
||||
url: 'https://store.asus.com/us/item/202009AM150000004/'
|
||||
}
|
||||
],
|
||||
name: 'asus'
|
||||
};
|
||||
|
||||
+42
-35
@@ -1,62 +1,69 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const BAndH: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: 'div[data-selenium="addToCartSection"]',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'black',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html?SID=s1600391647213ytuua52439',
|
||||
oosLabels: ['notify when available']
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593649-REG/asus_tuf_rtx3080_10g_gaming_tuf_gaming_geforce_rtx.html',
|
||||
oosLabels: ['notify when available']
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1452927-REG/evga_06g_p4_2063_kr_geforce_rtx_2060_xc.html'
|
||||
},
|
||||
// TUF was removed from BH, not sure why so commenting out listing for now
|
||||
// {
|
||||
// brand: 'asus',
|
||||
// model: 'tuf',
|
||||
// series: '3080',
|
||||
// url: 'https://www.bhphotovideo.com/c/product/1593649-REG/asus_tuf_rtx3080_10g_gaming_tuf_gaming_geforce_rtx.html'
|
||||
// },
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'gaming-oc',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html',
|
||||
oosLabels: ['notify when available']
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1592969-REG/zotac_zt_a30800d_10p_gaming_geforce_rtx_3080.html',
|
||||
oosLabels: ['notify when available']
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1592969-REG/zotac_zt_a30800d_10p_gaming_geforce_rtx_3080.html'
|
||||
},
|
||||
// TUF was removed from BH, not sure why so commenting out listing for now
|
||||
// {
|
||||
// brand: 'asus',
|
||||
// model: 'tuf oc',
|
||||
// series: '3080',
|
||||
// url: 'https://www.bhphotovideo.com/c/product/1593650-REG/asus_tuf_rtx3080_o10g_gaming_tuf_gaming_geforce_rtx.html'
|
||||
// },
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf-oc',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593650-REG/asus_tuf_rtx3080_o10g_gaming_tuf_gaming_geforce_rtx.html',
|
||||
oosLabels: ['notify when available']
|
||||
brand: 'msi',
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593996-REG/msi_g3080gxt10_geforce_rtx_3080_gaming.html'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'xtrio',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593996-REG/msi_g3080gxt10_geforce_rtx_3080_gaming.html',
|
||||
oosLabels: ['notify when available']
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593997-REG/msi_g3080v3x10c_geforce_rtx_3080_ventus.html'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593997-REG/msi_g3080v3x10c_geforce_rtx_3080_ventus.html',
|
||||
oosLabels: ['notify when available']
|
||||
model: 'gaming x trio - duplicate',
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593645-REG/msi_geforce_rtx_3080_gaming.html'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'TRIO2',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593645-REG/msi_geforce_rtx_3080_gaming.html',
|
||||
oosLabels: ['notify when available']
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus-oc',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html',
|
||||
oosLabels: ['notify when available']
|
||||
model: 'ventus 3x oc - duplicate',
|
||||
series: '3080',
|
||||
url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html'
|
||||
}
|
||||
|
||||
],
|
||||
name: 'bandh'
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const BestBuyCa: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '#root',
|
||||
text: ['available online']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/msi-nvidia-geforce-rtx-2060-super-gaming-x-8gb-gddr6-video-card/14419420?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/zotac-geforce-rtx-3080-trinity-10gb-gddr6x-video-card/14953249?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/msi-nvidia-geforce-rtx-3080-ventus-3x-10gb-gddr6x-video-card/14950588?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/evga-geforce-rtx-3080-xc3-ultra-gaming-10gb-gddr6x-video-card/14961449?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/asus-tuf-gaming-geforce-rtx-3080-10gb-gddr6x-video-card/14953248?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'rog strix',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/asus-rog-strix-geforce-rtx-3080-10gb-gddr6x-video-card/14954116?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/zotac-geforce-rtx-3090-trinity-24gb-gddr6x-video-card/14953250?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/asus-tuf-gaming-geforce-rtx-3090-24gb-gddr6x-video-card/14953247?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'rog strix',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/asus-rog-strix-geforce-rtx-3090-24gb-gddr6x-video-card/14954117?intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.ca/en-ca/product/msi-nvidia-geforce-rtx-3090-ventus-3x-oc-24gb-gddr6x-video-card/14966477?intl=nosplash'
|
||||
}
|
||||
],
|
||||
name: 'bestbuy-ca',
|
||||
waitUntil: 'domcontentloaded'
|
||||
};
|
||||
+78
-23
@@ -1,48 +1,103 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const BestBuy: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '.v-m-bottom-g',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.bestbuy.com/site/nvidia-geforce-rtx-2060-super-8gb-gddr6-pci-express-graphics-card-black-silver/6361329.p?skuId=6361329&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'nvidia',
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6429440/cart',
|
||||
model: 'founders edition',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/nvidia-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card-titanium-and-black/6429440.p?skuId=6429440&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6432445/cart',
|
||||
model: 'rog strix',
|
||||
url: 'https://www.bestbuy.com/site/asus-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-strix-graphics-card-black/6432445.p?skuId=6432445',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/asus-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-strix-graphics-card-black/6432445.p?skuId=6432445&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6432399/cart',
|
||||
model: 'xc3 black',
|
||||
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6432399.p?skuId=6432399',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6432399.p?skuId=6432399&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6432400/cart',
|
||||
model: 'xc3 ultra',
|
||||
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6432400.p?skuId=6432400',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6432400.p?skuId=6432400&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'black',
|
||||
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6430620.p?acampID=0&cmp=RMX&loc=Hatch&ref=198&skuId=6430620',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6430620/cart',
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6430620.p?acampID=0&cmp=RMX&loc=Hatch&ref=198&skuId=6430620&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6430621/cart',
|
||||
model: 'eagle oc',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6430621.p?skuId=6430621&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
cartUrl: 'https://api.bestbuy.com/click/-/6430175/cart',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.bestbuy.com/site/msi-geforce-rtx-3080-ventus-3x-10g-oc-bv-gddr6x-pci-express-4-0-graphic-card-black-silver/6430175.p?skuId=6430175&intl=nosplash'
|
||||
},
|
||||
{
|
||||
brand: 'nvidia',
|
||||
model: 'founders edition',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.com/site/nvidia-geforce-rtx-3090-24gb-gddr6x-pci-express-4-0-graphics-card-titanium-and-black/6429434.p?skuId=6429434'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'rog strix',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.com/site/asus-geforce-rtx-3090-24gb-gddr6x-pci-express-4-0-strix-graphics-card-black/6432447.p?skuId=6432447'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.com/site/asus-tuf-rtx-3090-24gb-gddr6x-pci-express-4-0-graphics-card-black/6432446.p?skuId=6432446'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.com/site/msi-geforce-rtx-3090-ventus-3x-24g-oc-bv-24gb-gddr6x-pci-express-4-0-graphics-card-black-silver/6430215.p?skuId=6430215'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'gaming',
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3090-24g-gddr6x-pci-express-4-0-graphics-card-black/6430623.p?skuId=6430623'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'eagle',
|
||||
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6430621.p?skuId=6430621',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x',
|
||||
url: 'https://www.bestbuy.com/site/msi-geforce-rtx-3080-ventus-3x-10g-oc-bv-gddr6x-pci-express-4-0-graphic-card-black-silver/6430175.p?skuId=6430175',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
},
|
||||
{
|
||||
brand: 'nvidia',
|
||||
model: 'founder edition',
|
||||
url: 'https://www.bestbuy.com/site/nvidia-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card-titanium-and-black/6429440.p?skuId=6429440',
|
||||
oosLabels: ['sold out', 'coming soon']
|
||||
series: '3090',
|
||||
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3090-24g-gddr6x-pci-express-4-0-graphics-card-black/6430624.p?skuId=6430624'
|
||||
}
|
||||
],
|
||||
name: 'bestbuy'
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const EvgaEu: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '.product-buy-specs',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3881-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3',
|
||||
series: '3080',
|
||||
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3895-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3883-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3885-KR'
|
||||
}
|
||||
],
|
||||
name: 'evga-eu'
|
||||
};
|
||||
+28
-10
@@ -1,30 +1,48 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Evga: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '.product-buy-specs',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=06G-P4-2065-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3881-KR',
|
||||
oosLabels: ['out of stock']
|
||||
series: '3080',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3881-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3897-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'ftw3',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3897-KR',
|
||||
oosLabels: ['out of stock']
|
||||
series: '3080',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3895-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 gaming',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3883-KR',
|
||||
oosLabels: ['out of stock']
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3883-KR'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra gaming',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3885-KR',
|
||||
oosLabels: ['out of stock']
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3885-KR'
|
||||
}
|
||||
],
|
||||
name: 'evga'
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import {Browser, Response} from 'puppeteer';
|
||||
import {NvidiaRegionInfo, regionInfos} from '../nvidia-api';
|
||||
import {Config} from '../../../config';
|
||||
import {Link} from '../store';
|
||||
import {Logger} from '../../../logger';
|
||||
import open from 'open';
|
||||
import {timestampUrlParameter} from '../../timestamp-url-parameter';
|
||||
|
||||
const nvidiaApiKey = '9485fa7b159e42edb08a83bde0d83dia';
|
||||
|
||||
function getRegionInfo(): NvidiaRegionInfo {
|
||||
const country = Array.from(regionInfos.keys()).includes(Config.store.country) ? Config.store.country : 'usa';
|
||||
|
||||
const defaultRegionInfo: NvidiaRegionInfo = {drLocale: 'en_us', fe2060SuperId: 5379432500, fe3080Id: 5438481700, fe3090Id: null, nvidiaLocale: 'en_us'};
|
||||
return regionInfos.get(country) ?? defaultRegionInfo;
|
||||
}
|
||||
|
||||
function digitalRiverStockUrl(id: number, drLocale: string): string {
|
||||
return `https://api.digitalriver.com/v1/shoppers/me/products/${id}/inventory-status?` +
|
||||
`&apiKey=${nvidiaApiKey}` +
|
||||
`&locale=${drLocale}` +
|
||||
timestampUrlParameter();
|
||||
}
|
||||
|
||||
interface NvidiaSessionTokenJSON {
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
function nvidiaSessionUrl(nvidiaLocale: string): string {
|
||||
return `https://store.nvidia.com/store/nvidia/SessionToken?format=json&locale=${nvidiaLocale}` +
|
||||
`&apiKey=${nvidiaApiKey}` +
|
||||
timestampUrlParameter();
|
||||
}
|
||||
|
||||
function addToCartUrl(id: number, drLocale: string, token: string): string {
|
||||
return 'https://api.digitalriver.com/v1/shoppers/me/carts/active/line-items?format=json&method=post' +
|
||||
`&productId=${id}` +
|
||||
`&token=${token}` +
|
||||
'&quantity=1' +
|
||||
`&locale=${drLocale}` +
|
||||
timestampUrlParameter();
|
||||
}
|
||||
|
||||
function checkoutUrl(drLocale: string, token: string): string {
|
||||
return `https://api.digitalriver.com/v1/shoppers/me/carts/active/web-checkout?token=${token}&locale=${drLocale}`;
|
||||
}
|
||||
|
||||
function fallbackCartUrl(nvidiaLocale: string): string {
|
||||
return `https://www.nvidia.com/${nvidiaLocale}/shop/geforce?${timestampUrlParameter()}`;
|
||||
}
|
||||
|
||||
export function generateSetupAction() {
|
||||
return async (browser: Browser) => {
|
||||
const {drLocale, nvidiaLocale} = getRegionInfo();
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
let response: Response | null;
|
||||
try {
|
||||
Logger.debug('creating cart/session token...');
|
||||
|
||||
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
||||
|
||||
if (response === null) {
|
||||
throw new Error('NvidiaAccessTokenUnavailable');
|
||||
}
|
||||
|
||||
const data = await response.json() as NvidiaSessionTokenJSON;
|
||||
const accessToken = data.access_token;
|
||||
const cartUrl = checkoutUrl(drLocale, accessToken);
|
||||
|
||||
Logger.debug(cartUrl);
|
||||
|
||||
if (Config.browser.open) {
|
||||
Logger.info('ℹ opening browser for user to login');
|
||||
|
||||
await open(cartUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error('✖ [nvidia] cannot generate cart/session token, continuing without; auto "add to cart" may not work', error);
|
||||
}
|
||||
|
||||
await page.close();
|
||||
};
|
||||
}
|
||||
|
||||
export function generateOpenCartAction(id: number, nvidiaLocale: string, drLocale: string, cardName: string) {
|
||||
return async (browser: Browser) => {
|
||||
const page = await browser.newPage();
|
||||
|
||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, starting auto add to cart 🚀🚀🚀`);
|
||||
|
||||
let response: Response | null;
|
||||
let cartUrl: string;
|
||||
try {
|
||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, getting access token 🚀🚀🚀`);
|
||||
|
||||
response = await page.goto(nvidiaSessionUrl(nvidiaLocale), {waitUntil: 'networkidle0'});
|
||||
if (response === null) {
|
||||
throw new Error('NvidiaAccessTokenUnavailable');
|
||||
}
|
||||
|
||||
const data = await response.json() as NvidiaSessionTokenJSON;
|
||||
const accessToken = data.access_token;
|
||||
|
||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, adding to cart 🚀🚀🚀`);
|
||||
|
||||
response = await page.goto(addToCartUrl(id, drLocale, accessToken), {waitUntil: 'networkidle0'});
|
||||
|
||||
Logger.info(`🚀🚀🚀 [nvidia] ${cardName}, opening checkout page 🚀🚀🚀`);
|
||||
|
||||
cartUrl = checkoutUrl(drLocale, accessToken);
|
||||
|
||||
Logger.info(cartUrl);
|
||||
|
||||
await open(cartUrl);
|
||||
} catch (error) {
|
||||
Logger.debug(error);
|
||||
Logger.error(`✖ [nvidia] ${cardName} could not automatically add to cart, opening page`, error);
|
||||
|
||||
cartUrl = fallbackCartUrl(nvidiaLocale);
|
||||
await open(cartUrl);
|
||||
}
|
||||
|
||||
await page.close();
|
||||
|
||||
return cartUrl;
|
||||
};
|
||||
}
|
||||
|
||||
export function generateLinks(): Link[] {
|
||||
const {drLocale, nvidiaLocale, fe3080Id, fe3090Id, fe2060SuperId} = getRegionInfo();
|
||||
|
||||
const links: Link[] = [];
|
||||
|
||||
if (fe2060SuperId) {
|
||||
links.push({
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
openCartAction: generateOpenCartAction(fe2060SuperId, nvidiaLocale, drLocale, 'TEST CARD debug'),
|
||||
series: 'test:series',
|
||||
url: digitalRiverStockUrl(fe2060SuperId, drLocale)
|
||||
});
|
||||
}
|
||||
|
||||
if (fe3080Id) {
|
||||
links.push({
|
||||
brand: 'nvidia',
|
||||
model: 'founders edition',
|
||||
openCartAction: generateOpenCartAction(fe3080Id, nvidiaLocale, drLocale, 'nvidia founders edition 3080'),
|
||||
series: '3080',
|
||||
url: digitalRiverStockUrl(fe3080Id, drLocale)
|
||||
});
|
||||
}
|
||||
|
||||
if (fe3090Id) {
|
||||
links.push({
|
||||
brand: 'nvidia',
|
||||
model: 'founders edition',
|
||||
openCartAction: generateOpenCartAction(fe3090Id, nvidiaLocale, drLocale, 'nvidia founders edition 3090'),
|
||||
series: '3090',
|
||||
url: digitalRiverStockUrl(fe3090Id, drLocale)
|
||||
});
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
+52
-13
@@ -1,30 +1,69 @@
|
||||
import {BestBuy} from './bestbuy';
|
||||
import {BAndH} from './bandh';
|
||||
import {Evga} from './evga';
|
||||
import {NewEgg} from './newegg';
|
||||
import {Adorama} from './adorama';
|
||||
import {Amazon} from './amazon';
|
||||
import {MicroCenter} from './microcenter';
|
||||
import {Config} from '../../config';
|
||||
import {Nvidia} from './nvidia';
|
||||
import {AmazonCa} from './amazon-ca';
|
||||
import {AmazonDe} from './amazon-de';
|
||||
import {Asus} from './asus';
|
||||
import {BAndH} from './bandh';
|
||||
import {BestBuy} from './bestbuy';
|
||||
import {BestBuyCa} from './bestbuy-ca';
|
||||
import {Config} from '../../config';
|
||||
import {Evga} from './evga';
|
||||
import {EvgaEu} from './evga-eu';
|
||||
import {Logger} from '../../logger';
|
||||
import {MicroCenter} from './microcenter';
|
||||
import {Newegg} from './newegg';
|
||||
import {NeweggCa} from './newegg-ca';
|
||||
import {Nvidia} from './nvidia';
|
||||
import {NvidiaApi} from './nvidia-api';
|
||||
import {OfficeDepot} from './officedepot';
|
||||
import {Store} from './store';
|
||||
import {Zotac} from './zotac';
|
||||
|
||||
const masterList = new Map([
|
||||
[Adorama.name, Adorama],
|
||||
[Amazon.name, Amazon],
|
||||
[AmazonCa.name, AmazonCa],
|
||||
[BestBuy.name, BestBuy],
|
||||
[AmazonDe.name, AmazonDe],
|
||||
[Asus.name, Asus],
|
||||
[BAndH.name, BAndH],
|
||||
[BestBuy.name, BestBuy],
|
||||
[BestBuyCa.name, BestBuyCa],
|
||||
[Evga.name, Evga],
|
||||
[EvgaEu.name, EvgaEu],
|
||||
[MicroCenter.name, MicroCenter],
|
||||
[NewEgg.name, NewEgg],
|
||||
[Nvidia.name, Nvidia]
|
||||
[Newegg.name, Newegg],
|
||||
[NeweggCa.name, NeweggCa],
|
||||
[Nvidia.name, Nvidia],
|
||||
[NvidiaApi.name, NvidiaApi],
|
||||
[OfficeDepot.name, OfficeDepot],
|
||||
[Zotac.name, Zotac]
|
||||
]);
|
||||
|
||||
const list = new Map();
|
||||
|
||||
for (const name of Config.stores) {
|
||||
list.set(name, masterList.get(name));
|
||||
for (const name of Config.store.stores) {
|
||||
if (masterList.has(name)) {
|
||||
list.set(name, masterList.get(name));
|
||||
} else {
|
||||
const logString = `No store named ${name}, skipping.`;
|
||||
Logger.warn(logString);
|
||||
}
|
||||
}
|
||||
|
||||
export const Stores = Array.from(list.values());
|
||||
Logger.info(`ℹ selected stores: ${Array.from(list.keys()).join(', ')}`);
|
||||
|
||||
if (Config.store.showOnlyBrands.length > 0) {
|
||||
Logger.info(`ℹ selected brands: ${Config.store.showOnlyBrands.join(', ')}`);
|
||||
}
|
||||
|
||||
if (Config.store.showOnlyModels.length > 0) {
|
||||
Logger.info(`ℹ selected models: ${Config.store.showOnlyModels.join(', ')}`);
|
||||
}
|
||||
|
||||
if (Config.store.showOnlySeries.length > 0) {
|
||||
Logger.info(`ℹ selected series: ${Config.store.showOnlySeries.join(', ')}`);
|
||||
}
|
||||
|
||||
export const Stores = Array.from(list.values()) as Store[];
|
||||
|
||||
export * from './store';
|
||||
|
||||
@@ -1,42 +1,92 @@
|
||||
import {Config} from '../../config';
|
||||
import {Store} from './store';
|
||||
|
||||
const MicroCenterLocation = Config.store.microCenterLocation;
|
||||
const microCenterLocationToId: Map<string, string> = new Map([
|
||||
['web', '029'],
|
||||
['brooklyn', '115'],
|
||||
['brentwood', '095'],
|
||||
['cambridge', '121'],
|
||||
['chicago', '151'],
|
||||
['columbus', '141'],
|
||||
['dallas', '131'],
|
||||
['devin', '181'],
|
||||
['duluth', '065'],
|
||||
['fairfax', '081'],
|
||||
['flushing', '145'],
|
||||
['houston', '155'],
|
||||
['madison-heights', '055'],
|
||||
['marietta', '041'],
|
||||
['mayfiend-heights', '051'],
|
||||
['north-jersey', '075'],
|
||||
['overland-park', '191'],
|
||||
['parkville', '125'],
|
||||
['rockville', '085'],
|
||||
['sharonville', '071'],
|
||||
['st-davids', '061'],
|
||||
['st-louis-park', '045'],
|
||||
['tustin', '101'],
|
||||
['westbury', '171'],
|
||||
['westmont', '025'],
|
||||
['yonkers', '105']
|
||||
]);
|
||||
|
||||
let storeId: string;
|
||||
if (microCenterLocationToId.get(MicroCenterLocation) === undefined) {
|
||||
storeId = '029';
|
||||
} else {
|
||||
storeId = microCenterLocationToId.get(MicroCenterLocation)!;
|
||||
}
|
||||
|
||||
export const MicroCenter: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '#cart-options',
|
||||
text: ['in stock']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: `https://www.microcenter.com/product/618433/evga-geforce-rtx-2060-ko-ultra-overclocked-dual-fan-6gb-gddr6-pcie-30-graphics-card/?storeid=${storeId}`
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra gaming',
|
||||
url: 'https://www.microcenter.com/product/628344/evga-geforce-rtx-3080-xc3-ultra-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card',
|
||||
oosLabels: ['sold out']
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: `https://www.microcenter.com/product/628344/evga-geforce-rtx-3080-xc3-ultra-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x overclocked',
|
||||
url: 'https://www.microcenter.com/product/628331/msi-geforce-rtx-3080-ventus-3x-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card',
|
||||
oosLabels: ['sold out']
|
||||
model: 'ventus 3x',
|
||||
series: '3080',
|
||||
url: `https://www.microcenter.com/product/628331/msi-geforce-rtx-3080-ventus-3x-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf gaming',
|
||||
url: 'https://www.microcenter.com/product/628303/asus-geforce-rtx-3080-tuf-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card',
|
||||
oosLabels: ['sold out']
|
||||
model: 'tuf',
|
||||
series: '3080',
|
||||
url: `https://www.microcenter.com/product/628303/asus-geforce-rtx-3080-tuf-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming x trio',
|
||||
url: 'https://www.microcenter.com/product/628330/msi-geforce-rtx-3080-gaming-x-trio-triple-fan-10gb-gddr6x-pcie-40-graphics-card',
|
||||
oosLabels: ['sold out']
|
||||
series: '3080',
|
||||
url: `https://www.microcenter.com/product/628330/msi-geforce-rtx-3080-gaming-x-trio-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
url: 'https://www.microcenter.com/product/628340/evga-geforce-rtx-3080-xc3-black-triple-fan-10gb-gddr6x-pcie-40-graphics-card',
|
||||
oosLabels: ['sold out']
|
||||
series: '3080',
|
||||
url: `https://www.microcenter.com/product/628340/evga-geforce-rtx-3080-xc3-black-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity overclocked',
|
||||
url: 'https://www.microcenter.com/product/628607/zotac-geforce-rtx-3080-trinity-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card',
|
||||
oosLabels: ['sold out']
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: `https://www.microcenter.com/product/628607/zotac-geforce-rtx-3080-trinity-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
|
||||
}
|
||||
],
|
||||
name: 'microcenter'
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const NeweggCa: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['are you a human?']
|
||||
},
|
||||
inStock: {
|
||||
container: '#landingpage-cart .btn-primary span',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.newegg.ca/evga-geforce-rtx-2060-06g-p4-2066-kr/p/N82E16814487488'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/evga-geforce-rtx-3080-10g-p5-3883-kr/p/N82E16814487521'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/evga-geforce-rtx-3080-10g-p5-3885-kr/p/N82E16814487520'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g-oc/p/N82E16814137598'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/msi-geforce-rtx-3080-rtx-3080-gaming-x-trio-10g/p/N82E16814137597'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3080-gv-n3080gaming-oc-10gd/p/N82E16814932329'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'eagle oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3080-gv-n3080eagle-oc-10gd/p/N82E16814932330'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/zotac-geforce-rtx-3080-zt-a30800d-10p/p/N82E16814500502'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.ca/asus-geforce-rtx-3080-tuf-rtx3080-o10g-gaming/p/N82E16814126452'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'gaming x trio',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.ca/msi-geforce-rtx-3090-rtx-3090-gaming-x-trio-24g/p/N82E16814137595'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
model: 'gaming oc',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3090-gv-n3090gaming-oc-24gd/p/N82E16814932327'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus 3x',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.ca/msi-geforce-rtx-3090-rtx-3090-ventus-3x-24g-oc/p/N82E16814137596'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.ca/zotac-geforce-rtx-3090-zt-a30900d-10p/p/N82E16814500503'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'tuf',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.ca/asus-geforce-rtx-3090-tuf-rtx3090-o24g-gaming/p/N82E16814126454'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
model: 'rog strix',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.ca/asus-geforce-rtx-3090-rog-strix-rtx3090-o24g-gaming/p/N82E16814126456'
|
||||
}
|
||||
],
|
||||
name: 'newegg-ca'
|
||||
};
|
||||
+164
-21
@@ -1,41 +1,184 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const NewEgg: Store = {
|
||||
export const Newegg: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['are you a human?']
|
||||
},
|
||||
inStock: {
|
||||
container: '#landingpage-cart .btn-primary span',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-2060-06g-p4-2066-kr/p/N82E16814487488'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814126453',
|
||||
model: 'tuf',
|
||||
url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453',
|
||||
oosLabels: ['auto notify', 'out of stock'],
|
||||
captchaLabels: ['are you a human?']
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'black gaming',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522',
|
||||
oosLabels: ['auto notify', 'out of stock'],
|
||||
captchaLabels: ['are you a human?']
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487522',
|
||||
model: 'xc3 black',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'argb led icx3',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3883-kr/p/N82E16814487521',
|
||||
oosLabels: ['auto notify', 'out of stock'],
|
||||
captchaLabels: ['are you a human?']
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487521',
|
||||
model: 'xc3',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3883-kr/p/N82E16814487521'
|
||||
},
|
||||
// Removed from Newegg currently not available in US
|
||||
// {
|
||||
// brand: 'evga',
|
||||
// cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487520',
|
||||
// model: 'xc3 ultra',
|
||||
// series: '3080',
|
||||
// url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3885-kr/p/N82E16814487520'
|
||||
// },
|
||||
{
|
||||
brand: 'evga',
|
||||
model: 'xc3 ultra gaming',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3885-kr/p/N82E16814487520',
|
||||
oosLabels: ['auto notify', 'out of stock'],
|
||||
captchaLabels: ['are you a human?']
|
||||
brand: 'msi',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137600',
|
||||
model: 'ventus 3x',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g/p/N82E16814137600'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
model: 'ventus',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g/p/N82E16814137600',
|
||||
oosLabels: ['auto notify', 'out of stock'],
|
||||
captchaLabels: ['are you a human?']
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137598',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g-oc/p/N82E16814137598'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137597',
|
||||
model: 'gaming x trio',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-gaming-x-trio-10g/p/N82E16814137597'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814932329',
|
||||
model: 'gaming oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/gigabyte-geforce-rtx-3080-gv-n3080gaming-oc-10gd/p/N82E16814932329'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814932330',
|
||||
model: 'eagle oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/gigabyte-geforce-rtx-3080-gv-n3080eagle-oc-10gd/p/N82E16814932330'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814500502',
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/zotac-geforce-rtx-3080-zt-a30800d-10p/p/N82E16814500502'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814126457',
|
||||
model: 'rog strix',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/asus-geforce-rtx-3080-rog-strix-rtx3080-o10g-gaming/p/N82E16814126457'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814126452',
|
||||
model: 'tuf oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-o10g-gaming/p/N82E16814126452'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814500504',
|
||||
model: 'trinity oc',
|
||||
series: '3080',
|
||||
url: 'https://www.newegg.com/zotac-geforce-rtx-3080-zt-t30800j-10p/p/N82E16814500504'
|
||||
},
|
||||
{
|
||||
brand: 'asus',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=',
|
||||
model: 'tuf',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/asus-geforce-rtx-3090-tuf-rtx3090-24g-gaming/p/N82E16814126455'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137595',
|
||||
model: 'gaming x trio',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3090-rtx-3090-gaming-x-trio-24g/p/N82E16814137595'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137596',
|
||||
model: 'ventus 3x oc',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3090-rtx-3090-ventus-3x-24g-oc/p/N82E16814137596'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814500503',
|
||||
model: 'trinity',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/zotac-geforce-rtx-3090-zt-a30900d-10p/p/N82E16814500503'
|
||||
},
|
||||
{
|
||||
brand: 'msi',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137599',
|
||||
model: 'ventus 3x',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/msi-geforce-rtx-3090-rtx-3090-ventus-3x-24g/p/N82E16814137599'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487525',
|
||||
model: 'ftw3 gaming',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3090-24g-p5-3985-kr/p/N82E16814487525'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487524',
|
||||
model: 'xc3 ultra gaming',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3090-24g-p5-3975-kr/p/N82E16814487524'
|
||||
},
|
||||
{
|
||||
brand: 'evga',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487526',
|
||||
model: 'ftw3 ultra gaming',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-3090-24g-p5-3987-kr/p/N82E16814487526'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814932327',
|
||||
model: 'gaming',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/gigabyte-geforce-rtx-3090-gv-n3090gaming-oc-24gd/p/N82E16814932327'
|
||||
},
|
||||
{
|
||||
brand: 'gigabyte',
|
||||
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814932328',
|
||||
model: 'eagle',
|
||||
series: '3090',
|
||||
url: 'https://www.newegg.com/gigabyte-geforce-rtx-3090-gv-n3090eagle-oc-24gd/p/N82E16814932328'
|
||||
}
|
||||
],
|
||||
name: 'newegg'
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import {generateLinks, generateSetupAction} from './helpers/nvidia';
|
||||
import {Store} from './store';
|
||||
|
||||
// Region/country set by config file, silently ignores null / missing values and defaults to usa
|
||||
|
||||
export interface NvidiaRegionInfo {
|
||||
drLocale: string;
|
||||
fe3080Id: number | null;
|
||||
fe3090Id: number | null;
|
||||
fe2060SuperId: number | null;
|
||||
nvidiaLocale: string;
|
||||
}
|
||||
|
||||
export const regionInfos = new Map<string, NvidiaRegionInfo>([
|
||||
['austria', {drLocale: 'de_de', fe2060SuperId: null, fe3080Id: 5440853700, fe3090Id: null, nvidiaLocale: 'de_de'}],
|
||||
['belgium', {drLocale: 'fr_fr', fe2060SuperId: 5394902700, fe3080Id: 5438795700, fe3090Id: null, nvidiaLocale: 'fr_fr'}],
|
||||
['canada', {drLocale: 'en_us', fe2060SuperId: null, fe3080Id: 5438481700, fe3090Id: null, nvidiaLocale: 'en_ca'}],
|
||||
['czechia', {drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438793800, fe3090Id: null, nvidiaLocale: 'en_gb'}],
|
||||
['denmark', {drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438793300, fe3090Id: null, nvidiaLocale: 'en_gb'}],
|
||||
['finland', {drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438793300, fe3090Id: null, nvidiaLocale: 'en_gb'}],
|
||||
['france', {drLocale: 'fr_fr', fe2060SuperId: null, fe3080Id: 5438795200, fe3090Id: null, nvidiaLocale: 'fr_fr'}],
|
||||
['germany', {drLocale: 'de_de', fe2060SuperId: null, fe3080Id: 5438792300, fe3090Id: null, nvidiaLocale: 'de_de'}],
|
||||
['great_britain', {drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438792800, fe3090Id: null, nvidiaLocale: 'en_gb'}],
|
||||
['ireland', {drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438792800, fe3090Id: null, nvidiaLocale: 'en_gb'}],
|
||||
['italy', {drLocale: 'it_it', fe2060SuperId: null, fe3080Id: 5438796200, fe3090Id: null, nvidiaLocale: 'it_it'}],
|
||||
['luxembourg', {drLocale: 'fr_fr', fe2060SuperId: 5394902700, fe3080Id: 5438795700, fe3090Id: null, nvidiaLocale: 'fr_fr'}],
|
||||
['netherlands', {drLocale: 'nl_nl', fe2060SuperId: 5394903500, fe3080Id: 5438796700, fe3090Id: null, nvidiaLocale: 'nl_nl'}],
|
||||
['poland', {drLocale: 'pl_pl', fe2060SuperId: null, fe3080Id: 5438797700, fe3090Id: null, nvidiaLocale: 'pl_pSl'}],
|
||||
['portugal', {drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438794300, fe3090Id: null, nvidiaLocale: 'en_gb'}],
|
||||
['russia', {drLocale: 'ru_ru', fe2060SuperId: null, fe3080Id: null, fe3090Id: null, nvidiaLocale: 'ru_ru'}],
|
||||
['spain', {drLocale: 'es_es', fe2060SuperId: null, fe3080Id: 5438794800, fe3090Id: null, nvidiaLocale: 'es_es'}],
|
||||
['sweden', {drLocale: 'sv_SE', fe2060SuperId: null, fe3080Id: 5438798100, fe3090Id: null, nvidiaLocale: 'sv_se'}],
|
||||
['usa', {drLocale: 'en_us', fe2060SuperId: 5379432500, fe3080Id: 5438481700, fe3090Id: null, nvidiaLocale: 'en_us'}]
|
||||
]);
|
||||
|
||||
export const NvidiaApi: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: 'body',
|
||||
text: ['product_inventory_in_stock']
|
||||
}
|
||||
},
|
||||
links: generateLinks(),
|
||||
name: 'nvidia-api',
|
||||
setupAction: generateSetupAction()
|
||||
};
|
||||
@@ -1,12 +1,34 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Nvidia: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['are you a human?']
|
||||
},
|
||||
inStock: {
|
||||
container: '.main-container',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.newegg.com/evga-geforce-rtx-2060-06g-p4-2066-kr/p/N82E16814487488'
|
||||
},
|
||||
{
|
||||
brand: 'nvidia',
|
||||
model: 'founders edition',
|
||||
url: 'https://api.digitalriver.com/v1/shoppers/me/products/5438481700/inventory-status?apiKey=9485fa7b159e42edb08a83bde0d83dia',
|
||||
oosLabels: ['product_inventory_out_of_stock']
|
||||
series: '3080',
|
||||
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/?page=1&limit=9&locale=en-us&gpu=RTX%203080&category=GPU&manufacturer=NVIDIA&gpu_filter=RTX%203090~1,RTX%203080~1,RTX%203070~0,TITAN%20RTX~0,RTX%202080%20Ti~0,RTX%202070%20SUPER~0,RTX%202060%20SUPER~0,RTX%202060~0,GTX%201660%20Ti~0,GTX%201660%20SUPER~0,GTX%201660~0,GTX%201650%20SUPER~0,GTX%201650~0'
|
||||
},
|
||||
{
|
||||
brand: 'nvidia',
|
||||
model: 'founders edition',
|
||||
series: '3090',
|
||||
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/?page=1&limit=9&locale=en-us&gpu=RTX%203090&category=GPU&manufacturer=NVIDIA&gpu_filter=RTX%203090~1,RTX%203080~1,RTX%203070~0,TITAN%20RTX~0,RTX%202080%20Ti~0,RTX%202070%20SUPER~0,RTX%202060%20SUPER~0,RTX%202060~0,GTX%201660%20Ti~0,GTX%201660%20SUPER~0,GTX%201660~0,GTX%201650%20SUPER~0,GTX%201650~0'
|
||||
}
|
||||
],
|
||||
name: 'nvidia'
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const OfficeDepot: Store = {
|
||||
labels: {
|
||||
captcha: {
|
||||
container: 'body',
|
||||
text: ['please verify you are a human']
|
||||
},
|
||||
inStock: {
|
||||
container: '#productPurchase',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://www.officedepot.com/a/products/4652239/EVGA-GeForce-RTX-2060-Graphic-Card/'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8',
|
||||
series: '3080',
|
||||
url: 'https://www.officedepot.com/a/products/7189374/PNY-GeForce-RTX-3080-10GB-GDDR6X/'
|
||||
},
|
||||
{
|
||||
brand: 'pny',
|
||||
model: 'xlr8 rgb',
|
||||
series: '3080',
|
||||
url: 'https://www.officedepot.com/a/products/7791294/PNY-GeForce-RTX-3080-10GB-GDDR6X/'
|
||||
}
|
||||
],
|
||||
name: 'officedepot'
|
||||
};
|
||||
@@ -1,13 +1,29 @@
|
||||
interface Link {
|
||||
brand: string;
|
||||
import {Browser, LoadEvent} from 'puppeteer';
|
||||
|
||||
export type Element = {
|
||||
container: string;
|
||||
text: string[];
|
||||
};
|
||||
|
||||
export type Link = {
|
||||
brand: 'test:brand' | 'asus' | 'evga' | 'gigabyte' | 'pny' | 'msi' | 'nvidia' | 'zotac';
|
||||
series: 'test:series' | '3070' | '3080' | '3090';
|
||||
model: string;
|
||||
url: string;
|
||||
oosLabels: string[];
|
||||
captchaLabels?: string[];
|
||||
}
|
||||
|
||||
export interface Store {
|
||||
cartUrl?: string;
|
||||
openCartAction?: (browser: Browser) => Promise<string>;
|
||||
screenshot?: string;
|
||||
};
|
||||
|
||||
export type Labels = {
|
||||
captcha?: Element;
|
||||
inStock: Element;
|
||||
};
|
||||
|
||||
export type Store = {
|
||||
links: Link[];
|
||||
labels: Labels;
|
||||
name: string;
|
||||
}
|
||||
setupAction?: (browser: Browser) => void;
|
||||
waitUntil?: LoadEvent;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import {Store} from './store';
|
||||
|
||||
export const Zotac: Store = {
|
||||
labels: {
|
||||
inStock: {
|
||||
container: '.add-to-cart-wrapper',
|
||||
text: ['add to cart']
|
||||
}
|
||||
},
|
||||
links: [
|
||||
{
|
||||
brand: 'test:brand',
|
||||
model: 'test:model',
|
||||
series: 'test:series',
|
||||
url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-2060-twin-fan-zt-t20600f-10m'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity',
|
||||
series: '3080',
|
||||
url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-3080-trinity-zt-a30800d-10p'
|
||||
},
|
||||
{
|
||||
brand: 'zotac',
|
||||
model: 'trinity OC',
|
||||
series: '3080',
|
||||
url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-3080-trinity-oc-zt-a30800j-10p'
|
||||
}
|
||||
],
|
||||
name: 'zotac'
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generates unique URL param to prevent cached responses (similar to jQuery that Nvidia uses)
|
||||
*
|
||||
* @return string in format &=1111111111111 (time since epoch in ms)
|
||||
*/
|
||||
export function timestampUrlParameter(): string {
|
||||
return `&_=${Date.now()}`;
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare module 'pushbullet';
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import {Config} from './config';
|
||||
import {Page} from 'puppeteer';
|
||||
import {disableBlockerInPage} from './adblocker';
|
||||
|
||||
export function getSleepTime() {
|
||||
return Config.browser.minSleep + (Math.random() * (Config.browser.maxSleep - Config.browser.minSleep));
|
||||
}
|
||||
|
||||
export async function delay(ms: number) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export async function closePage(page: Page) {
|
||||
await disableBlockerInPage(page);
|
||||
await page.close();
|
||||
}
|
||||
+2
-1
@@ -11,6 +11,7 @@
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user