Compare commits

..

95 Commits

Author SHA1 Message Date
github-actions[bot] f9ed774595 chore: release 1.5.0 (#103)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-23 23:13:49 -04:00
Jef LeCompte c53cfd67c1 chore(store): normalize amazon-de links
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 23:06:11 -04:00
admon84 e1b34a9ccf feat: filter models (#261) 2020-09-23 22:53:19 -04:00
Yu-Po Luke Chen d6a27c988c feat(store): add microcenter store location config (#215)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 22:43:56 -04:00
manybot 8a70f14743 feat(store): add amazon-de (#167)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
Co-authored-by: fuckingrobot <fuckingrobot@users.noreply.github.com>
2020-09-23 22:16:15 -04:00
Jef LeCompte 02ea40b6b2 docs: update mobile carriers
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 22:13:39 -04:00
bubba1337 47dab22d57 chore(notification): additional ca carriers (#248) 2020-09-23 22:08:38 -04:00
Jef LeCompte 676e793ef3 docs: update 3090 stores
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 22:07:17 -04:00
Mark Dietzer dd45dba82c feat(stores): add 3090 for bestbuy, newegg (#249) 2020-09-23 22:06:03 -04:00
serg06 482fb58cbf feat(stores): add 3090s for amazon-ca, bestbuy-ca, newegg-ca (#258)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 22:05:21 -04:00
Jef LeCompte 8913879593 feat: multiple discord roles and webhooks, qol for envs (#260) 2020-09-23 22:02:41 -04:00
admon84 9a53917586 fix(store): adorama captcha config (#234) 2020-09-23 22:02:26 -04:00
Vincent ab61a98bea chore(notification): warn user of improper sms and email config (#259) 2020-09-23 22:01:31 -04:00
Jef LeCompte 9f470f06e9 feat: deprecate nvidia (api), add 3080 add 3090
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 21:03:47 -04:00
Jef LeCompte 4cb52d31d5 chore: remove old cards
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 20:48:55 -04:00
Jef LeCompte 6aed674ee1 chore: add banner, only show sound if enabled
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 19:38:17 -04:00
Jef LeCompte bcbd2b6007 chore: change color dep, add banner
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 19:20:17 -04:00
Jef LeCompte 76b28a6dbd fix: color logs and notification
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 18:43:25 -04:00
Jef LeCompte a154e95681 docs: update configuration notes
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 18:07:09 -04:00
George 0ad67fe204 feat(log): colors for console logs (#207)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 17:55:27 -04:00
Jef LeCompte 2bd8446960 hotfix: series filter
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 16:05:27 -04:00
Jef LeCompte 257dd0e615 chore(store): normalize model
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 14:41:27 -04:00
Scott Cooper c78d9a98ba chore: add types for brand and series (#148)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 14:33:38 -04:00
Jef LeCompte 7c50e2b5aa chore(misc): normalize logs (#242) 2020-09-23 14:20:06 -04:00
serg06 8466f9f398 chore(misc): tiny improvements (#233)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 11:02:44 -04:00
serg06 22fd22fe74 feat(store): add bestbuy.ca (#229) 2020-09-23 10:56:23 -04:00
dependabot[bot] 98c8b1e7b8 chore(deps): bump puppeteer from 5.3.0 to 5.3.1 (#237)
Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/puppeteer/puppeteer/releases)
- [Commits](https://github.com/puppeteer/puppeteer/compare/v5.3.0...v5.3.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-23 10:53:47 -04:00
fuckingrobot 74490eae3a feat(notification): add pushbullet, add url with notifications (#226)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 10:45:04 -04:00
Andrew Mackrodt 20656805c1 feat: add chromium sandbox skipping (#209) 2020-09-23 08:35:56 -04:00
admon84 6608a79769 feat: invert logic (#141)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 23:33:36 -04:00
Colin Farrell a70e63bb35 chore(store): add netherland nvidia (#210)
Co-authored-by: fuckingrobot <fuckingrobot@users.noreply.github.com>
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 21:59:10 -04:00
Bryan Berger 908ed35882 feat(notification): twitter integration (#224) 2020-09-22 21:49:40 -04:00
Jef LeCompte 9d5f430119 chore(store): normalize amazon links
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 20:34:29 -04:00
bmalaski ede9f6f5a6 chore(store): strix on amazon (#206)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 20:30:05 -04:00
Jef LeCompte 136ccf3593 chore(store): add filter newegg
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 19:56:27 -04:00
Jef LeCompte 9d66683528 chore(store): add filter newegg
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 19:51:06 -04:00
Jef LeCompte cf0eac2b23 chore: add sort linting (#169) 2020-09-22 19:31:49 -04:00
bmalaski 6409646d57 fix(store): bandh removed cards (#201) 2020-09-22 19:29:28 -04:00
Paolo Moretti 605bdd7ca7 feat(store): add evga eu (#172)
Co-authored-by: fuckingrobot <fuckingrobot@users.noreply.github.com>
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 19:19:55 -04:00
Sachin Umashankar 8e7d267549 chore(store): add errors for bandh (#180)
Co-authored-by: fuckingrobot <fuckingrobot@users.noreply.github.com>
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 19:17:37 -04:00
levelfifty 190388cfe4 feat(store): add evga model (#220) 2020-09-22 19:15:23 -04:00
Jef LeCompte 1d63733681 docs: add zotac
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 19:13:44 -04:00
AndrewKurniawan 78758552b2 feat(store): add zotac store (#214) 2020-09-22 19:12:17 -04:00
dependabot[bot] 14c36bebe7 chore(deps): bump messaging-api-telegram from 1.0.0 to 1.0.1 (#196)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-22 02:29:51 -04:00
dependabot[bot] 327f7b080a chore(deps-dev): bump @types/node from 14.11.1 to 14.11.2 (#197)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.11.1 to 14.11.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-22 02:28:41 -04:00
Jef LeCompte 4f7db895e2 docs: update faq 2020-09-21 23:54:42 -04:00
Jef LeCompte 3b51c02873 docs: update readme 2020-09-21 20:13:46 -04:00
fuckingrobot ebd6091a09 feat: set country in config, login to nvidia when starting (#162) 2020-09-21 20:12:45 -04:00
Jef LeCompte 3b9a1d2ea8 hotfix: missing quote 2020-09-21 19:30:05 -04:00
Jef LeCompte adb603b776 chore: remove unknown affiliate 2020-09-21 19:25:25 -04:00
Jef LeCompte 2c6866ad7f chore: remove unknown affliate 2020-09-21 19:24:07 -04:00
George 71c6774511 feat(store): add bannedSeller label for stores (#173)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-21 18:54:40 -04:00
Jef LeCompte 5f628d2c12 chore(store): add errors for evga (#176) 2020-09-21 18:24:46 -04:00
Jef LeCompte 4dfdb9eb2e chore: make SCREENSHOT default true
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-21 16:18:56 -04:00
designgears 12d25eb710 chore(store): officedepot oos label (#171) 2020-09-21 15:26:02 -04:00
xdMatthewbx 9675c5b8d6 fix(notification): change discord ping visibility (#168)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
Co-authored-by: bmalaski <5288648+bmalaski@users.noreply.github.com>
2020-09-21 14:50:40 -04:00
bmalaski 0df2dcfbd4 feat(store): add office depot (#157)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-21 12:54:45 -04:00
xdMatthewbx 76f5849889 feat(store): add newegg.ca (#160)
closes #159
2020-09-21 12:48:00 -04:00
Nachi G 6413144c1c feat: temporarily pause requests if store has stock (#147)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-21 11:38:18 -04:00
George 133a54fa17 feat: bestbuy bypass international splash, newegg add to cart (#153) 2020-09-21 11:36:25 -04:00
bmalaski debd8f57da chore(store): pny listings (#156)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-21 08:34:09 -04:00
fuckingrobot 3ea146da14 feat: update for complex add to cart, fix nvidia (#108) 2020-09-21 08:24:20 -04:00
Nachi G 722eaf3cd6 feat(notification): add desktop notifications (#140)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 20:48:27 -04:00
Jordan Garcia 7191e03a80 feat: include screenshot for emails + sms notifications (#144) 2020-09-20 20:42:20 -04:00
Mark Dietzer 0f6e570cc8 fix: memory leak due to adblocker (#139) 2020-09-20 19:38:31 -04:00
JoeSchofield 7fc1e776fd docs: update pushover variables (#136) 2020-09-20 18:26:57 -04:00
Mark Dietzer a75d214dd5 fix(nvidia): false positives (#132)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 17:46:45 -04:00
admon84 19c8f188c7 fix: newegg out-of-stock labels (#134) 2020-09-20 17:45:25 -04:00
Jef LeCompte d42736bf22 chore: update funding 2020-09-20 16:56:35 -04:00
Jef LeCompte d2893f6826 chore: update funding 2020-09-20 16:55:40 -04:00
Jef LeCompte 07b2da4fe9 chore(store): filter amazon third party vendors (#126) 2020-09-20 16:45:56 -04:00
admon84 770a13ac35 fix: newegg out-of-stock (#124) 2020-09-20 16:19:36 -04:00
admon84 d9be3fe618 feat: custom user agent (#121)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 15:56:47 -04:00
admon84 252459d5d3 feat: card series filter, fix: newegg oosLabels (#120) 2020-09-20 15:51:44 -04:00
Jef LeCompte 7efdcd8f07 chore: change test:notification to run explicitly
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 14:36:00 -04:00
Mark Dietzer 4f83b3b233 feat: add delay on captcha to try and evade faster (#119) 2020-09-20 14:27:10 -04:00
Jef LeCompte c0352961a9 refactor: extract lookup (#117) 2020-09-20 14:13:29 -04:00
Mark Dietzer dc0f710674 feat(scraping): change lookup impl, add randomize sleep (#110)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 13:52:37 -04:00
Jef LeCompte 2e5d13bda5 docs: PLAY_SOUND note 2020-09-20 13:48:45 -04:00
Jef LeCompte 2a1f15041e chore: move notification test, other refactoring (#111) 2020-09-20 13:18:32 -04:00
bmalaski a3fc07daf0 feat(notification): discord integration (#82)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 12:36:05 -04:00
bmalaski 5b91065043 feat(store): add adorama (#104)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 12:14:56 -04:00
Jef LeCompte e819e46116 fix: keep single Store from draining
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 12:00:25 -04:00
Christopher Sauer 39a478050a docs: add pushover api generation link (#107) 2020-09-20 11:26:38 -04:00
Jef LeCompte 28947be9bc fix: rateLimitTimeout not being defaulted (#106)
refactor: `browser` and `store` config object
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 11:02:57 -04:00
Jef LeCompte 3de1f81eb1 hotfix: make HEADLESS default true
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 10:39:44 -04:00
Jordan Garcia d1a5aa1f02 feat: load puppeteer faster, run stores in parallel (#83)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 10:28:45 -04:00
Forrest Marvez a501cf703b feat(store): add asus (#102)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 10:10:00 -04:00
anethema c65fa04666 fix(store): false positives for nvidia. (#85)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 09:44:49 -04:00
Jef LeCompte a538809db5 chore: update pr template
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 09:29:02 -04:00
Jef LeCompte 85faaa1ca7 chore: update desc of variables (#101)
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 09:15:57 -04:00
Mark Dietzer 103d96dc81 fix(notification): wrong condition for sounds playing (#91)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 08:47:37 -04:00
Wulfre df5ba68e94 chore(store): normalize model names (#96)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-20 08:36:19 -04:00
Lukas b7d9462e79 fix: check response for rate limiting (#58) (#98)
Co-authored-by: Lukas Szimtenings <lszimtenings@ukaachen.de>
2020-09-20 08:33:47 -04:00
George 1e9d8fec42 refactor: move cartUrl from Store to Link (#87) 2020-09-19 22:49:09 -04:00
54 changed files with 3431 additions and 630 deletions
+9 -2
View File
@@ -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
View File
@@ -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=""
+2
View File
@@ -1 +1,3 @@
github: jef
patreon: hijef
custom: ["https://www.paypal.me/jxf"]
+3
View File
@@ -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. -->
+56
View File
@@ -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)
+95 -28
View File
@@ -2,6 +2,8 @@
[FAQ](#FAQ) | [Issues](https://github.com/jef/nvidia-snatcher/issues) | [Wiki](https://github.com/jef/nvidia-snatcher/wiki)
![nvidia-snatcher](media/screenshot.png)
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

+1157 -157
View File
File diff suppressed because it is too large Load Diff
+30 -5
View File
@@ -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"
}
}
}
+26
View File
@@ -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);
+11
View File
@@ -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);
}
+12
View File
@@ -0,0 +1,12 @@
import chalk from 'chalk';
export const banner = chalk.green.bold(`
$$\\ $$\\ $$\\ $$\\ $$\\
\\__| $$ |\\__| $$ | $$ |
$$$$$$$\\ $$\\ $$\\ $$\\ $$$$$$$ |$$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\
$$ __$$\\\\$$\\ $$ |$$ |$$ __$$ |$$ | \\____$$\\ $$$$$$\\ $$ _____|$$ __$$\\ \\____$$\\\\_$$ _| $$ _____|$$ __$$\\ $$ __$$\\ $$ __$$\\
$$ | $$ |\\$$\\$$ / $$ |$$ / $$ |$$ | $$$$$$$ |\\______|\\$$$$$$\\ $$ | $$ | $$$$$$$ | $$ | $$ / $$ | $$ |$$$$$$$$ |$$ | \\__|
$$ | $$ | \\$$$ / $$ |$$ | $$ |$$ |$$ __$$ | \\____$$\\ $$ | $$ |$$ __$$ | $$ |$$\\ $$ | $$ | $$ |$$ ____|$$ |
$$ | $$ | \\$ / $$ |\\$$$$$$$ |$$ |\\$$$$$$$ | $$$$$$$ |$$ | $$ |\\$$$$$$$ | \\$$$$ |\\$$$$$$$\\ $$ | $$ |\\$$$$$$$\\ $$ |
\\__| \\__| \\_/ \\__| \\_______|\\__| \\_______| \\_______/ \\__| \\__| \\_______| \\____/ \\_______|\\__| \\__| \\_______|\\__|
`);
+103 -28
View File
@@ -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
View File
@@ -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
View File
@@ -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}`;
}
+14
View File
@@ -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');
})();
}
+38
View File
@@ -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
View File
@@ -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');
}
});
}
+57 -20
View File
@@ -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);
}
}
+22
View File
@@ -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');
}
});
}
+12 -10
View File
@@ -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');
}
});
}
+14 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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');
});
});
});
}
}
+8 -5
View File
@@ -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);
}
})();
}
+29
View File
@@ -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');
}
});
}
+62
View File
@@ -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)
);
}
+4 -4
View File
@@ -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
View File
@@ -1,2 +1 @@
export * from './lookup';
export * from './includes-labels';
+110 -61
View File
@@ -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);
}
+77
View File
@@ -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'
};
+48 -43
View File
@@ -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'
+101
View File
@@ -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
View File
@@ -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'
+32
View File
@@ -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
View File
@@ -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'
};
+74
View File
@@ -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
View File
@@ -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'
+37
View File
@@ -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
View File
@@ -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'
+167
View File
@@ -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
View File
@@ -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';
+66 -16
View File
@@ -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'
+119
View File
@@ -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
View File
@@ -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'
+46
View File
@@ -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()
};
+24 -2
View File
@@ -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'
+35
View File
@@ -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'
};
+24 -8
View File
@@ -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;
};
+32
View File
@@ -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'
};
+8
View File
@@ -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()}`;
}
+1
View File
@@ -0,0 +1 @@
declare module 'pushbullet';
+18
View File
@@ -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
View File
@@ -11,6 +11,7 @@
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"sourceMap": true
}
}