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 root = true
[*] [*]
end_of_line = lf
insert_final_newline = true
indent_style = tab 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" # ** All configuration variables are optional **
EMAIL_PASSWORD="secretpassword" # Read https://github.com/jef/nvidia-snatcher#customization for help on customizing this file
NOTIFICATION_TEST="false" #############################################################################################
PAGE_TIMEOUT="30000"
RATE_LIMIT_TIMEOUT="5000" BROWSER_TRUSTED=""
SLACK_CHANNEL="SlackChannelName" DISCORD_NOTIFY_GROUP=""
SLACK_TOKEN="slack-token" DISCORD_WEB_HOOK=""
STORES="bestbuy,bandh,nvidia" EMAIL_USERNAME=""
PHONE_NUMBER="1234567890" EMAIL_PASSWORD=""
PHONE_CARRIER="tmobile" HEADLESS=""
PUSHOVER_TOKEN="123pushover-token456" IN_STOCK_WAIT_TIME=""
PUSHOVER_USER="123pushover-user-key" LOG_LEVEL=""
OPEN_BROWSER="true" MICROCENTER_LOCATION=""
PLAY_SOUND="false" OPEN_BROWSER=""
SCREENSHOT="true" 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_ACCESS_TOKEN=""
TELEGRAM_CHAT_ID="1234" TELEGRAM_CHAT_ID=""
SHOW_ONLY_BRANDS="evga" 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 github: jef
patreon: hijef
custom: ["https://www.paypal.me/jxf"]
+3
View File
@@ -7,6 +7,9 @@
<!-- Fixes #(issue) --> <!-- Fixes #(issue) -->
<!-- Please also include relevant motivation and context. --> <!-- 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 ### Testing
<!-- Please describe the tests that you ran to verify your changes. --> <!-- Please describe the tests that you ran to verify your changes. -->
+56
View File
@@ -1,5 +1,61 @@
# Changelog # 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) ## [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) [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. 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 - 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 :) > :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** | | | **Adorama** | **Amazon** | **Amazon (CA)** | **ASUS** | **B&H** | **Best Buy** | **Best Buy (CA)** | **EVGA** | **Micro Center** | **Newegg** | **Newegg (CA)** | **Nvidia** | **Office Depot** | **Zotac** |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **3070**| | | | | | | | | **3070**| | | | | | | | | | | | | | |
| **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | | **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` |
| **3090** | | | | | | | | | **3090** | | | | | | `✔` | `✔` | | | `✔` | `✔` | `✔` | | |
## Installation and prerequisites ## Installation and prerequisites
@@ -56,57 +58,119 @@ At any point you want the program to stop, use <kbd>Ctrl</kbd> + <kbd>C</kbd>.
### Customization ### 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: Here is a list of variables that you can use to customize your newly copied `.env` file:
| **Environment variable** | **Description** | | **Environment variable** | **Description** | **Notes** |
|:---:|---| |:---:|---|---|
| `EMAIL_USERNAME` | Gmail address (e.g., `jensen.robbed.us@gmail.com`); optional | | `BROWSER_TRUSTED` | Skip Chromium Sandbox | Useful for containerized environments, default: `false` |
| `EMAIL_PASSWORD` | Gmail password; see below if you have MFA; optional | | `DESKTOP_NOTIFICATIONS` | Display desktop notifications using [node-notifier](https://www.npmjs.com/package/node-notifier) | Default: `false` |
| `NOTIFICATION_TEST` | Test all the notifications configured; optional, default: `false` | | `DISCORD_NOTIFY_GROUP` | Discord group you would like to notify | Can be comma separated, use role ID, E.g.: `<@2834729847239842>` |
| `PAGE_TIMEOUT` | Navigation Timeout in milliseconds (`0` for infinite); optional, default: `30000` | | `DISCORD_WEB_HOOK` | Discord Web Hook URL | Can be comma separated, use whole webhook URL |
| `PHONE_NUMBER` | 10 digit phone number (e.g., `1234567890`); optional, email configuration required | | `EMAIL_USERNAME` | Gmail address | E.g.: `jensen.robbed.us@gmail.com` |
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS; optional, email configuration required | | `EMAIL_PASSWORD` | Gmail password | See below if you have MFA |
| `RATE_LIMIT_TIMEOUT` | Rate limit timeout for each full store cycle; optional, default: `5000` | | `HEADLESS` | Puppeteer to run headless or not | Debugging related, default: `true` |
| `SHOW_ONLY_BRANDS` | If set, will only show specified brands, seperated by `,` | | `IN_STOCK_WAIT_TIME` | Time to wait between requests to the same store if it has cards in stock | In seconds, default: `0` |
| `SLACK_CHANNEL` | Slack channel for posting (e.g., `update`); optional | | `LOG_LEVEL` | [Logging levels](https://github.com/winstonjs/winston#logging-levels) | Debugging related, default: `info` |
| `SLACK_TOKEN` | Slack API token; optional | | `MICROCENTER_LOCATION` | Specific MicroCenter location to search | Default : `web` |
| `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` |
| `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` |
| `PLAY_SOUND` | Play this sound notification if a card is found.; optional | | `PHONE_NUMBER` | 10 digit phone number | E.g.: `1234567890`, email configuration required |
| `SCREENSHOT` | Capture screenshot of page on successful hit; optional, default `true` | | `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS | Email configuration required |
| `TELEGRAM_ACCESS_TOKEN` | Telegram access token; optional | | `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/) |
| `TELEGRAM_CHAT_ID` | Telegram chat ID; optional | | `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: 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 #### Supported stores
| **Stores** | **Environment variable** | | **Stores** | **Environment variable** |
|:---:|:---:| |:---:|:---:|
| Best Buy | `bestbuy`| | Adorama | `adorama`|
| Amazon.ca | `amazon-ca`|
| Amazon | `amazon`| | Amazon | `amazon`|
| Amazon (CA) | `amazon-ca`|
| Amazon (DE) | `amazon-de`|
| ASUS | `asus` |
| B&H | `bandh`| | B&H | `bandh`|
| Best Buy | `bestbuy`|
| Best Buy (CA) | `bestbuy-ca`|
| EVGA | `evga`| | EVGA | `evga`|
| EVGA (EU) | `evga-eu`|
| Micro Center | `microcenter`| | Micro Center | `microcenter`|
| Newegg | `newegg`| | Newegg | `newegg`|
| Newegg (CA) | `newegg-ca`|
| Nvidia | `nvidia`| | Nvidia | `nvidia`|
| Nvidia (API) | `nvidia-api`|
| Office Depot | `officedepot`|
| Zotac | `zotac`|
#### Supported carriers #### Supported carriers
| **Carrier** | **Environment variable** | **Notes** | | **Carrier** | **Environment variable** | **Notes** |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| AT&T | `att`| | | AT&T | `att`| |
| Bell | `bell` | |
| Fido | `fido` | |
| Google | `google`| | | Google | `google`| |
| Koodo | `koodo` | |
| Mint | `mint`| | | Mint | `mint`| |
| Rogers | `rogers` | |
| Sprint | `sprint`| | | Sprint | `sprint`| |
| Telus | `telus`| | | Telus | `telus`| |
| T-Mobile | `tmobile`| | | T-Mobile | `tmobile`| |
| Verizon | `verizon`| Works with Visible | | 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 ## 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: 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: 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). **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: Special shout to initial developers:
- [@andirew](https://github.com/andirew) - [@andirew](https://github.com/andirew)
- [@davidlbowman](https://github.com/davidlbowman)
- [@fuckingrobot](https://github.com/fuckingrobot) - [@fuckingrobot](https://github.com/fuckingrobot)
- [@ioncaza](https://github.com/IonCaza) - [@ioncaza](https://github.com/IonCaza)
- [@malbert69](https://github.com/malbert69) - [@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", "name": "nvidia-snatcher",
"version": "1.4.0", "version": "1.5.0",
"description": "🔮 For all your Nvidia needs", "description": "🔮 For all your Nvidia needs",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
"build": "rimraf ./build && tsc", "build": "rimraf ./build && tsc",
"lint": "xo", "lint": "xo",
"lint:fix": "xo --fix", "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": { "repository": {
"type": "git", "type": "git",
@@ -21,22 +22,46 @@
}, },
"homepage": "https://github.com/jef/nvidia-snatcher#readme", "homepage": "https://github.com/jef/nvidia-snatcher#readme",
"dependencies": { "dependencies": {
"chalk": "^4.1.0",
"dotenv": "^8.2.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", "nodemailer": "^6.4.11",
"open": "^7.2.1", "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", "pushover-notifications": "^1.2.2",
"twitter": "^1.7.1",
"winston": "^3.3.3" "winston": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@slack/web-api": "^5.12.0", "@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/nodemailer": "^6.4.0",
"@types/puppeteer": "^3.0.2", "@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", "play-sound": "^1.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.0.2", "typescript": "^4.0.2",
"xo": "^0.33.1" "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 {banner} from './banner';
import {config} from 'dotenv'; 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 = { const notifications = {
desktop: process.env.DESKTOP_NOTIFICATIONS === 'true',
discord: {
notifyGroup: envOrArray(process.env.DISCORD_NOTIFY_GROUP),
webHookUrl: envOrArray(process.env.DISCORD_WEB_HOOK)
},
email: { email: {
username: process.env.EMAIL_USERNAME ?? '', password: envOrString(process.env.EMAIL_PASSWORD),
password: process.env.EMAIL_PASSWORD ?? '' username: envOrString(process.env.EMAIL_USERNAME)
}, },
phone: { phone: {
availableCarriers: new Map([ availableCarriers: new Map([
['att', 'txt.att.net'], ['att', 'txt.att.net'],
['bell', 'txt.bell.ca'],
['fido', 'fido.ca'],
['google', 'msg.fi.google.com'], ['google', 'msg.fi.google.com'],
['koodo', 'msg.koodomobile.com'],
['mint', 'mailmymobile.net'], ['mint', 'mailmymobile.net'],
['rogers', 'pcs.rogers.com'],
['sprint', 'messaging.sprintpcs.com'], ['sprint', 'messaging.sprintpcs.com'],
['telus', 'msg.telus.com'], ['telus', 'msg.telus.com'],
['tmobile', 'tmomail.net'], ['tmobile', 'tmomail.net'],
['verizon', 'vtext.com'] ['verizon', 'vtext.com'],
['virgin', 'vmobl.com'],
['virgin-ca', 'vmobile.ca']
]), ]),
carrier: process.env.PHONE_CARRIER ?? '', carrier: envOrString(process.env.PHONE_CARRIER),
number: process.env.PHONE_NUMBER ?? '' number: envOrString(process.env.PHONE_NUMBER)
}, },
playSound: process.env.PLAY_SOUND ?? 'false', playSound: envOrString(process.env.PLAY_SOUND),
pushBulletApiKey: envOrString(process.env.PUSHBULLET),
pushover: { pushover: {
token: process.env.PUSHOVER_TOKEN, token: envOrString(process.env.PUSHOVER_TOKEN),
user: process.env.PUSHOVER_USER username: envOrString(process.env.PUSHOVER_USER)
}, },
slack: { slack: {
channel: process.env.SLACK_CHANNEL ?? '', channel: envOrString(process.env.SLACK_CHANNEL),
token: process.env.SLACK_TOKEN ?? '' token: envOrString(process.env.SLACK_TOKEN)
}, },
telegram: { telegram: {
accessToken: process.env.TELEGRAM_ACCESS_TOKEN ?? '', accessToken: envOrString(process.env.TELEGRAM_ACCESS_TOKEN),
chatId: process.env.TELEGRAM_CHAT_ID ?? '' 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 = { const page = {
capture: process.env.SCREENSHOT ?? 'true',
width: 1920,
height: 1080, height: 1080,
navigationTimeout: Number(process.env.PAGE_TIMEOUT) ?? 30000, inStockWaitTime: envOrNumber(process.env.IN_STOCK_WAIT_TIME),
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36' 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 store = {
const rateLimitTimeout = Number(process.env.RATE_LIMIT_TIMEOUT) ?? 5000; country: envOrString(process.env.COUNTRY, 'usa'),
const stores = process.env.STORES ? process.env.STORES.split(',') : ['nvidia']; microCenterLocation: envOrString(process.env.MICROCENTER_LOCATION, 'web'),
const showOnlyBrands = process.env.SHOW_ONLY_BRANDS ? process.env.SHOW_ONLY_BRANDS.split(',') : []; 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 = { export const Config = {
browser,
logLevel,
notifications, notifications,
rateLimitTimeout,
page, page,
stores, store
openBrowser,
showOnlyBrands
}; };
+37 -22
View File
@@ -1,34 +1,50 @@
import {Config} from './config'; import {Config} from './config';
import {Stores} from './store/model';
import {Logger} from './logger'; import {Logger} from './logger';
import {sendNotification} from './notification'; import {Stores} from './store/model';
import {lookup} from './store'; import {adBlocker} from './adblocker';
import puppeteer from 'puppeteer'; 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. * Starts the bot.
*/ */
async function main() { async function main() {
const results = []; if (Stores.length === 0) {
const browser = await puppeteer.launch(); 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) { for (const store of Stores) {
Logger.debug(store.links); 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 { try {
void main(); void main();
} catch (error) { } catch (error) {
// Ignoring errors; more than likely due to rate limits Logger.error('✖ something bad happened, resetting nvidia-snatcher', error);
Logger.error(error);
void main(); void main();
} }
+45 -5
View File
@@ -1,4 +1,7 @@
import {Link, Store} from './store/model';
import winston, {format} from 'winston'; import winston, {format} from 'winston';
import {Config} from './config';
import chalk from 'chalk';
const prettyJson = format.printf(info => { const prettyJson = format.printf(info => {
const timestamp = new Date().toLocaleTimeString(); const timestamp = new Date().toLocaleTimeString();
@@ -7,11 +10,10 @@ const prettyJson = format.printf(info => {
info.message = JSON.stringify(info.message, null, 4); 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({ export const Logger = winston.createLogger({
level: process.env.LOG_LEVEL ?? 'info',
format: format.combine( format: format.combine(
format.colorize(), format.colorize(),
format.prettyPrint(), format.prettyPrint(),
@@ -19,7 +21,45 @@ export const Logger = winston.createLogger({
format.simple(), format.simple(),
prettyJson prettyJson
), ),
transports: [ level: Config.logLevel,
new winston.transports.Console({}) 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 {Link, Store} from '../store/model';
import Mail from 'nodemailer/lib/mailer'; import {Logger, Print} from '../logger';
import {Config} from '../config'; import {Config} from '../config';
import {Logger} from '../logger'; import Mail from 'nodemailer/lib/mailer';
import nodemailer from 'nodemailer';
const email = Config.notifications.email; const email = Config.notifications.email;
const subject = 'NVIDIA - BUY NOW';
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
service: 'gmail',
auth: { auth: {
user: email.username, pass: email.password,
pass: email.password user: email.username
} },
service: 'gmail'
}); });
const mailOptions: Mail.Options = { export function sendEmail(link: Link, store: Store) {
from: email.username, const mailOptions: Mail.Options = {
to: email.username, attachments: link.screenshot ? [
subject {
}; 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) { transporter.sendMail(mailOptions, error => {
mailOptions.text = text;
transporter.sendMail(mailOptions, (error, info) => {
if (error) { if (error) {
Logger.error(error); Logger.error('✖ couldn\'t send email', error);
} else { } else {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.info('✔ email sent');
Logger.info(`✔ email sent: ${info.response}`);
} }
}); });
} }
+57 -20
View File
@@ -1,38 +1,75 @@
import {Link, Store} from '../store/model';
import {Config} from '../config'; import {Config} from '../config';
import {sendEmail} from './email'; import {Logger} from '../logger';
import {sendSMS} from './sms';
import {playSound} from './sound'; 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 {sendPushoverNotification} from './pushover';
import {sendSMS} from './sms';
import {sendSlackMessage} from './slack';
import {sendTelegramMessage} from './telegram'; import {sendTelegramMessage} from './telegram';
import {sendTweet} from './twitter';
const notifications = Config.notifications; const notifications = Config.notifications;
export function sendNotification(cartUrl: string) { export function sendNotification(link: Link, store: Store) {
if (notifications.email.username && notifications.email.password) { if (notifications.email.username && notifications.email.password) {
sendEmail(cartUrl); Logger.debug('↗ sending email');
} sendEmail(link, store);
if (notifications.slack.channel && notifications.slack.token) {
sendSlackMessage(cartUrl);
}
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
sendTelegramMessage(cartUrl);
} }
if (notifications.phone.number) { 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)) { if (carrier && notifications.phone.availableCarriers.has(carrier)) {
sendSMS(cartUrl); sendSMS(link, store);
} }
} }
if (notifications.pushover.token && notifications.pushover.user) { if (notifications.playSound) {
sendPushoverNotification(cartUrl); Logger.debug('↗ playing sound');
}
if (notifications.playSound === 'true') {
playSound(); 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 {Config} from '../config';
import {Logger} from '../logger'; import Push from 'pushover-notifications';
const pushover = Config.notifications.pushover; const pushover = Config.notifications.pushover;
const push = new Push({ 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 = { const message = {
message: text message: link.cartUrl ? link.cartUrl : link.url,
title: Print.inStock(link, store)
}; };
push.send(message, (err: Error, result: string) => { push.send(message, (error: Error) => {
if (err) { if (error) {
Logger.error(err); Logger.error('✖ couldn\'t send pushover message', error);
} else { } else {
Logger.info(`Pushover notification sent: ${result}`); Logger.info('pushover message sent');
} }
}); });
} }
+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 {Config} from '../config';
import {Logger} from '../logger'; import {WebClient} from '@slack/web-api';
const channel = Config.notifications.slack.channel; const channel = Config.notifications.slack.channel;
const token = Config.notifications.slack.token; const token = Config.notifications.slack.token;
const web = new WebClient(token); const web = new WebClient(token);
export function sendSlackMessage(text: string) { export function sendSlackMessage(link: Link, store: Store) {
(async () => { (async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
try { try {
const result = await web.chat.postMessage({text, channel}); const result = await web.chat.postMessage({
channel,
text: `${Print.inStock(link, store)}\n${givenUrl}`
});
if (!result.ok) { if (!result.ok) {
Logger.error(result.error); Logger.error('✖ couldn\'t send slack message', result);
return; return;
} }
Logger.info(`✔ slack message sent to '${channel}': ${text}`); Logger.info('✔ slack message sent');
} catch (error) { } 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 {Link, Store} from '../store/model';
import Mail from 'nodemailer/lib/mailer'; import {Logger, Print} from '../logger';
import {Config} from '../config'; 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 [email, phone] = [Config.notifications.email, Config.notifications.phone];
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
service: 'gmail',
auth: { auth: {
user: email.username, pass: email.password,
pass: email.password user: email.username
} },
service: 'gmail'
}); });
const mailOptions: Mail.Options = { export function sendSMS(link: Link, store: Store) {
from: Config.notifications.email.username, const mailOptions: Mail.Options = {
to: generateAddress(), attachments: link.screenshot ? [
subject {
}; 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) { transporter.sendMail(mailOptions, error => {
mailOptions.text = text;
transporter.sendMail(mailOptions, (error, info) => {
if (error) { if (error) {
Logger.error(error); Logger.error('✖ couldn\'t send sms', error);
} else { } else {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.info('✔ sms sent');
Logger.info(`✔ sms sent: ${info.response}`);
} }
}); });
} }
function generateAddress() { function generateAddress() {
const carrier = phone.carrier.toLowerCase(); const carrier = phone.carrier;
if (carrier && phone.availableCarriers.has(carrier)) { if (carrier && phone.availableCarriers.has(carrier)) {
return [phone.number, phone.availableCarriers.get(carrier)].join('@'); return [phone.number, phone.availableCarriers.get(carrier)].join('@');
} }
Logger.error('✖ unknown carrier', carrier);
} }
+28 -17
View File
@@ -1,25 +1,36 @@
import playerLib = require('play-sound');
import {Config} from '../config'; import {Config} from '../config';
import {Logger} from '../logger'; 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; let player: any;
const player = playerLib();
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() { export function playSound() {
// Check if file exists if (player.player !== null) {
fs.access(notificationSound, fs.constants.F_OK, err => { fs.access(Config.notifications.playSound, fs.constants.F_OK, error => {
if (err) { if (error) {
Logger.error(`error opening sound file: ${err.message}`); Logger.error(`error opening sound file: ${error.message}`);
return; return;
}
player.play(notificationSound, (err: string) => {
Logger.info('✔ playing sound');
if (err) {
Logger.error(`error playing sound: ${err}`);
} }
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 {Config} from '../config';
import {Logger} from '../logger';
import {TelegramClient} from 'messaging-api-telegram'; import {TelegramClient} from 'messaging-api-telegram';
const telegram = Config.notifications.telegram; const telegram = Config.notifications.telegram;
@@ -8,13 +9,15 @@ const client = new TelegramClient({
accessToken: telegram.accessToken accessToken: telegram.accessToken
}); });
export function sendTelegramMessage(text: string) { export function sendTelegramMessage(link: Link, store: Store) {
(async () => { (async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
try { try {
await client.sendMessage(telegram.chatId, text); await client.sendMessage(telegram.chatId, `${Print.inStock(link, store)}\n${givenUrl}`);
Logger.info(`✔ telegram message sent to '${telegram.chatId}': ${text}`); Logger.info('✔ telegram message sent');
} catch (error) { } catch (error) {
Logger.error(error); Logger.error('✖ couldn\'t send telegram message', error);
} }
})(); })();
} }
+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 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(); 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 './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 {Config} from '../config';
import {Logger} from '../logger'; import {filterStoreLink} from './filter';
import open from 'open';
import {Store} from './model';
import {sendNotification} from '../notification';
import {includesLabels} from './includes-labels'; import {includesLabels} from './includes-labels';
import open from 'open';
import {sendNotification} from '../notification';
/** const inStock: Record<string, boolean> = {};
* 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);
}
/** /**
* Responsible for looking up information about a each product within * Responsible for looking up information about a each product within
* a `Store`. It's important that we ignore `no-await-in-loop` here * 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. * 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. * @param store Vendor of graphics cards.
*/ */
export async function lookup(browser: puppeteer.Browser, store: Store) { async function lookup(browser: Browser, store: Store) {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
for (const link of store.links) { for (const link of store.links) {
if (!filterBrand(link.brand)) { if (!filterStoreLink(link)) {
continue; continue;
} }
const page = await browser.newPage(); const page = await browser.newPage();
page.setDefaultNavigationTimeout(Config.page.navigationTimeout); page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
await page.setUserAgent(Config.page.userAgent); await page.setUserAgent(Config.page.userAgent);
await page.setViewport({
height: Config.page.height,
width: Config.page.width
});
const graphicsCard = `${link.brand} ${link.model}`;
try { try {
await page.goto(link.url, {waitUntil: 'networkidle0'}); await lookupCard(browser, store, page, link);
} catch { } catch (error) {
Logger.error(`✖ [${store.name}] ${graphicsCard} skipping; timed out`); Logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`);
await page.close();
continue;
} }
const bodyHandle = await page.$('body'); await closePage(page);
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();
} }
/* 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'; import {Store} from './store';
export const AmazonCa: 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: [ links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.ca/dp/B07PBLD2MX'
},
{ {
brand: 'msi', brand: 'msi',
model: 'gaming trio', model: 'gaming x trio',
url: 'https://www.amazon.ca/MSI-GeForce-RTX-3080-10G/dp/B08HR7SV3M?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR7SV3M'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'ftw3gaming', model: 'ftw3',
url: 'https://www.amazon.ca/EVGA-GeForce-Technology-Backplate-10G-P5-3895-KR/dp/B08HR3DPGW?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR3DPGW'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'ftw3ultra', model: 'ftw3 ultra',
url: 'https://www.amazon.ca/EVGA-GeForce-Technology-Backplate-10G-P5-3897-KR/dp/B08HR3Y5GQ?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR3Y5GQ'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'xc3ultra', model: 'xc3 ultra',
url: 'https://www.amazon.ca/EVGA-GeForce-Cooling-Backplate-10G-P5-3885-KR/dp/B08HR55YB5?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR55YB5'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'xc3gaming', model: 'xc3',
url: 'https://www.amazon.ca/EVGA-GeForce-Cooling-Backplate-10G-P5-3883-KR/dp/B08HR4RJ3Q?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR4RJ3Q'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'xc3black', model: 'xc3 black',
url: 'https://www.amazon.ca/EVGA-GeForce-Gaming-Cooling-10G-P5-3881-KR/dp/B08HR6FMF3?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR6FMF3'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'gigabyte', brand: 'gigabyte',
model: 'windforce', model: 'gaming oc',
url: 'https://www.amazon.ca/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080GAMING/dp/B08HJTH61J?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HJTH61J'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'gigabyte', brand: 'gigabyte',
model: 'windforce eagle', model: 'eagle oc',
url: 'https://www.amazon.ca/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080EAGLE/dp/B08HJS2JLJ?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HJS2JLJ'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'asus', brand: 'asus',
model: 'tuf', model: 'tuf',
url: 'https://www.amazon.ca/Asus-90YV0FB0-M0AM00-TUF-RTX3080-10G-GAMING/dp/B08HHDP9DW?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HHDP9DW'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'asus', brand: 'asus',
model: 'tufoc', model: 'tuf oc',
url: 'https://www.amazon.ca/Asus-90YV0FB1-M0AM00-TUF-RTX3080-O10G-GAMING/dp/B08HH5WF97?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HH5WF97'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'ventus', model: 'ventus 3x oc',
url: 'https://www.amazon.ca/MSI-GeForce-RTX-3080-10G/dp/B08HR5SXPS?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.ca/dp/B08HR5SXPS'
captchaLabels: ['enter the characters you see below']
} }
], ],
name: 'amazon-ca' 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'; import {Store} from './store';
export const Amazon: 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: [ links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.com/dp/B07MQ36Z6L'
},
{ {
brand: 'pny', brand: 'pny',
model: 'xlr8', model: 'xlr8',
url: 'https://www.amazon.com/PNY-GeForce-Gaming-Epic-X-Graphics/dp/B08HBR7QBM?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HBR7QBM'
captchaLabels: ['enter the characters you see below'] },
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.amazon.com/dp/B08HBTJMLJ'
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'gaming trio', model: 'gaming x trio',
url: 'https://www.amazon.com/MSI-GeForce-RTX-3080-10G/dp/B08HR7SV3M?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HR7SV3M'
captchaLabels: ['enter the characters you see below'] },
{
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', brand: 'evga',
model: 'ftw3', model: 'ftw3',
url: 'https://www.amazon.com/EVGA-10G-P5-3897-KR-GeForce-Technology-Backplate/dp/B08HR3Y5GQ?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HR3DPGW'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'xc3', model: 'xc3',
url: 'https://www.amazon.com/EVGA-10G-P5-3885-KR-GeForce-Cooling-Backplate/dp/B08HR55YB5?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HR4RJ3Q'
captchaLabels: ['enter the characters you see below'] },
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR6FMF3'
}, },
{ {
brand: 'gigabyte', brand: 'gigabyte',
model: 'windforce', model: 'gaming oc',
url: 'https://www.amazon.com/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080GAMING/dp/B08HJTH61J?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HJTH61J'
captchaLabels: ['enter the characters you see below']
}, },
{ {
brand: 'gigabyte', brand: 'gigabyte',
model: 'windforce eagle', model: 'eagle oc',
url: 'https://www.amazon.com/GIGABYTE-GeForce-Graphics-WINDFORCE-GV-N3080EAGLE/dp/B08HJS2JLJ?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HJS2JLJ'
captchaLabels: ['enter the characters you see below'] },
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.amazon.com/dp/B08HH5WF97'
}, },
{ {
brand: 'asus', brand: 'asus',
model: 'tuf', model: 'tuf',
url: 'https://www.amazon.com/ASUS-Graphics-DisplayPort-Military-Grade-Certification/dp/B08HH5WF97?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HHDP9DW'
captchaLabels: ['enter the characters you see below'] },
{
brand: 'asus',
model: 'strix',
series: '3080',
url: 'https://www.amazon.com/dp/B08J6F174Z'
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'ventus', model: 'ventus 3x oc',
url: 'https://www.amazon.com/MSI-GeForce-RTX-3080-10G/dp/B08HR5SXPS?ref_=ast_sto_dp', series: '3080',
oosLabels: ['currently unavailable'], url: 'https://www.amazon.com/dp/B08HR5SXPS'
captchaLabels: ['enter the characters you see below'] },
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.amazon.com/dp/B08HJNKT3P'
} }
], ],
name: 'amazon' 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'; import {Store} from './store';
export const BAndH: Store = { export const BAndH: Store = {
labels: {
inStock: {
container: 'div[data-selenium="addToCartSection"]',
text: ['add to cart']
}
},
links: [ links: [
{ {
brand: 'gigabyte', brand: 'test:brand',
model: 'black', model: 'test:model',
url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html?SID=s1600391647213ytuua52439', series: 'test:series',
oosLabels: ['notify when available'] url: 'https://www.bhphotovideo.com/c/product/1452927-REG/evga_06g_p4_2063_kr_geforce_rtx_2060_xc.html'
},
{
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']
}, },
// 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', brand: 'gigabyte',
model: 'gaming-oc', model: 'gaming oc',
url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html', series: '3080',
oosLabels: ['notify when available'] url: 'https://www.bhphotovideo.com/c/product/1593333-REG/gigabyte_gv_n3080gaming_oc_10gd_geforce_rtx_3080_gaming.html'
}, },
{ {
brand: 'zotac', brand: 'zotac',
model: 'trinity', model: 'trinity',
url: 'https://www.bhphotovideo.com/c/product/1592969-REG/zotac_zt_a30800d_10p_gaming_geforce_rtx_3080.html', series: '3080',
oosLabels: ['notify when available'] 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', brand: 'msi',
model: 'tuf-oc', model: 'gaming x trio',
url: 'https://www.bhphotovideo.com/c/product/1593650-REG/asus_tuf_rtx3080_o10g_gaming_tuf_gaming_geforce_rtx.html', series: '3080',
oosLabels: ['notify when available'] url: 'https://www.bhphotovideo.com/c/product/1593996-REG/msi_g3080gxt10_geforce_rtx_3080_gaming.html'
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'xtrio', model: 'ventus 3x oc',
url: 'https://www.bhphotovideo.com/c/product/1593996-REG/msi_g3080gxt10_geforce_rtx_3080_gaming.html', series: '3080',
oosLabels: ['notify when available'] url: 'https://www.bhphotovideo.com/c/product/1593997-REG/msi_g3080v3x10c_geforce_rtx_3080_ventus.html'
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'ventus', model: 'gaming x trio - duplicate',
url: 'https://www.bhphotovideo.com/c/product/1593997-REG/msi_g3080v3x10c_geforce_rtx_3080_ventus.html', series: '3080',
oosLabels: ['notify when available'] url: 'https://www.bhphotovideo.com/c/product/1593645-REG/msi_geforce_rtx_3080_gaming.html'
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'TRIO2', model: 'ventus 3x oc - duplicate',
url: 'https://www.bhphotovideo.com/c/product/1593645-REG/msi_geforce_rtx_3080_gaming.html', series: '3080',
oosLabels: ['notify when available'] url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html'
},
{
brand: 'msi',
model: 'ventus-oc',
url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html',
oosLabels: ['notify when available']
} }
], ],
name: 'bandh' 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'; import {Store} from './store';
export const BestBuy: Store = { export const BestBuy: Store = {
labels: {
inStock: {
container: '.v-m-bottom-g',
text: ['add to cart']
}
},
links: [ 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', brand: 'asus',
cartUrl: 'https://api.bestbuy.com/click/-/6432445/cart',
model: 'rog strix', 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', series: '3080',
oosLabels: ['sold out', 'coming soon'] 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', brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6432399/cart',
model: 'xc3 black', 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', series: '3080',
oosLabels: ['sold out', 'coming soon'] 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', brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6432400/cart',
model: 'xc3 ultra', 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', series: '3080',
oosLabels: ['sold out', 'coming soon'] 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', brand: 'gigabyte',
model: 'black', cartUrl: 'https://api.bestbuy.com/click/-/6430620/cart',
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', model: 'gaming oc',
oosLabels: ['sold out', 'coming soon'] 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', brand: 'gigabyte',
model: 'eagle', 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', series: '3090',
oosLabels: ['sold out', 'coming soon'] url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3090-24g-gddr6x-pci-express-4-0-graphics-card-black/6430624.p?skuId=6430624'
},
{
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']
} }
], ],
name: 'bestbuy' 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'; import {Store} from './store';
export const Evga: Store = { export const Evga: Store = {
labels: {
inStock: {
container: '.product-buy-specs',
text: ['add to cart']
}
},
links: [ 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', brand: 'evga',
model: 'xc3 black', model: 'xc3 black',
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3881-KR', series: '3080',
oosLabels: ['out of stock'] 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', brand: 'evga',
model: 'ftw3', model: 'ftw3',
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3897-KR', series: '3080',
oosLabels: ['out of stock'] url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3895-KR'
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'xc3 gaming', model: 'xc3',
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3883-KR', series: '3080',
oosLabels: ['out of stock'] url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3883-KR'
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'xc3 ultra gaming', model: 'xc3 ultra',
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3885-KR', series: '3080',
oosLabels: ['out of stock'] url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3885-KR'
} }
], ],
name: 'evga' 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 {Adorama} from './adorama';
import {BAndH} from './bandh';
import {Evga} from './evga';
import {NewEgg} from './newegg';
import {Amazon} from './amazon'; import {Amazon} from './amazon';
import {MicroCenter} from './microcenter';
import {Config} from '../../config';
import {Nvidia} from './nvidia';
import {AmazonCa} from './amazon-ca'; 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([ const masterList = new Map([
[Adorama.name, Adorama],
[Amazon.name, Amazon], [Amazon.name, Amazon],
[AmazonCa.name, AmazonCa], [AmazonCa.name, AmazonCa],
[BestBuy.name, BestBuy], [AmazonDe.name, AmazonDe],
[Asus.name, Asus],
[BAndH.name, BAndH], [BAndH.name, BAndH],
[BestBuy.name, BestBuy],
[BestBuyCa.name, BestBuyCa],
[Evga.name, Evga], [Evga.name, Evga],
[EvgaEu.name, EvgaEu],
[MicroCenter.name, MicroCenter], [MicroCenter.name, MicroCenter],
[NewEgg.name, NewEgg], [Newegg.name, Newegg],
[Nvidia.name, Nvidia] [NeweggCa.name, NeweggCa],
[Nvidia.name, Nvidia],
[NvidiaApi.name, NvidiaApi],
[OfficeDepot.name, OfficeDepot],
[Zotac.name, Zotac]
]); ]);
const list = new Map(); const list = new Map();
for (const name of Config.stores) { for (const name of Config.store.stores) {
list.set(name, masterList.get(name)); 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'; export * from './store';
+66 -16
View File
@@ -1,42 +1,92 @@
import {Config} from '../../config';
import {Store} from './store'; 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 = { export const MicroCenter: Store = {
labels: {
inStock: {
container: '#cart-options',
text: ['in stock']
}
},
links: [ 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', brand: 'evga',
model: 'xc3 ultra gaming', model: 'xc3 ultra',
url: 'https://www.microcenter.com/product/628344/evga-geforce-rtx-3080-xc3-ultra-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card', series: '3080',
oosLabels: ['sold out'] 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', brand: 'msi',
model: 'ventus 3x overclocked', model: 'ventus 3x',
url: 'https://www.microcenter.com/product/628331/msi-geforce-rtx-3080-ventus-3x-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card', series: '3080',
oosLabels: ['sold out'] 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', brand: 'asus',
model: 'tuf gaming', model: 'tuf',
url: 'https://www.microcenter.com/product/628303/asus-geforce-rtx-3080-tuf-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card', series: '3080',
oosLabels: ['sold out'] 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', brand: 'msi',
model: 'gaming x trio', 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', series: '3080',
oosLabels: ['sold out'] 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', brand: 'evga',
model: 'xc3 black', model: 'xc3 black',
url: 'https://www.microcenter.com/product/628340/evga-geforce-rtx-3080-xc3-black-triple-fan-10gb-gddr6x-pcie-40-graphics-card', series: '3080',
oosLabels: ['sold out'] 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', brand: 'zotac',
model: 'trinity overclocked', model: 'trinity',
url: 'https://www.microcenter.com/product/628607/zotac-geforce-rtx-3080-trinity-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card', series: '3080',
oosLabels: ['sold out'] 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' 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'; 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: [ 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', brand: 'asus',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814126453',
model: 'tuf', model: 'tuf',
url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453', series: '3080',
oosLabels: ['auto notify', 'out of stock'], url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453'
captchaLabels: ['are you a human?']
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'black gaming', cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487522',
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522', model: 'xc3 black',
oosLabels: ['auto notify', 'out of stock'], series: '3080',
captchaLabels: ['are you a human?'] url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3881-kr/p/N82E16814487522'
}, },
{ {
brand: 'evga', brand: 'evga',
model: 'argb led icx3', cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487521',
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3883-kr/p/N82E16814487521', model: 'xc3',
oosLabels: ['auto notify', 'out of stock'], series: '3080',
captchaLabels: ['are you a human?'] 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', brand: 'msi',
model: 'xc3 ultra gaming', cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137600',
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3885-kr/p/N82E16814487520', model: 'ventus 3x',
oosLabels: ['auto notify', 'out of stock'], series: '3080',
captchaLabels: ['are you a human?'] url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g/p/N82E16814137600'
}, },
{ {
brand: 'msi', brand: 'msi',
model: 'ventus', cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137598',
url: 'https://www.newegg.com/msi-geforce-rtx-3080-rtx-3080-ventus-3x-10g/p/N82E16814137600', model: 'ventus 3x oc',
oosLabels: ['auto notify', 'out of stock'], series: '3080',
captchaLabels: ['are you a human?'] 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' 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'; import {Store} from './store';
export const Nvidia: Store = { export const Nvidia: Store = {
labels: {
captcha: {
container: 'body',
text: ['are you a human?']
},
inStock: {
container: '.main-container',
text: ['add to cart']
}
},
links: [ 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', brand: 'nvidia',
model: 'founders edition', model: 'founders edition',
url: 'https://api.digitalriver.com/v1/shoppers/me/products/5438481700/inventory-status?apiKey=9485fa7b159e42edb08a83bde0d83dia', series: '3080',
oosLabels: ['product_inventory_out_of_stock'] 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' 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 { import {Browser, LoadEvent} from 'puppeteer';
brand: string;
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; model: string;
url: string; url: string;
oosLabels: string[];
captchaLabels?: string[];
}
export interface Store {
cartUrl?: string; cartUrl?: string;
openCartAction?: (browser: Browser) => Promise<string>;
screenshot?: string;
};
export type Labels = {
captcha?: Element;
inStock: Element;
};
export type Store = {
links: Link[]; links: Link[];
labels: Labels;
name: string; 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, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true,
"sourceMap": true
} }
} }