Compare commits

..

192 Commits

Author SHA1 Message Date
github-actions[bot] 317c987b08 chore: release 1.6.0 (#525)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-29 23:01:45 -04:00
James Kiefer d7360f71ef fix: update pushbullet import to match package (#637) 2020-10-29 17:00:58 -04:00
Ted Morin ab4f779573 chore(bestbuy-ca): add 3070 founders edition (#634)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-29 16:58:28 -04:00
Jef LeCompte f04c669143 chore: don't build Dockerfile if not necessary
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-29 16:57:30 -04:00
Jef LeCompte 72d66ab127 chore: bump dependencies
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-29 16:52:37 -04:00
dependabot[bot] cc114afd15 chore(deps-dev): bump webpack from 5.3.0 to 5.3.2 (#636)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-29 16:51:23 -04:00
dependabot[bot] 7c16f85abc chore(deps-dev): bump @types/node from 14.14.5 to 14.14.6 (#626)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-29 16:51:14 -04:00
Jef LeCompte 8dd10424b3 chore: change dependencies
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-29 16:29:57 -04:00
Jef LeCompte 13edbf311e refactor: easier comparables, fix audits (#614) 2020-10-29 16:27:09 -04:00
AchillesPDX e5b0d7983b chore(bestbuy): add 3070 models (#630) 2020-10-29 16:26:44 -04:00
AchillesPDX 3976b21907 chore(evga): add 3070 cards (#631) 2020-10-29 13:10:33 -04:00
KillahInstinct1 87f1c0191d chore: add 3070 cards and correct models on 3080 (#627)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-29 10:57:30 -04:00
PaddseL 9e0f767a1d chore(store): add several 3070 cards to several german stores (#628)
Co-authored-by: fubu2k <48277447+fubu2k@users.noreply.github.com>
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-29 10:57:07 -04:00
serg06 2353ee2ef8 chore(store): add 3070 to nvidia, best-buy-ca, newegg-ca (#622) 2020-10-29 10:54:12 -04:00
dependabot[bot] 6b9ecaeb3d chore(deps): bump puppeteer from 5.4.0 to 5.4.1 (#617)
Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 5.4.0 to 5.4.1.
- [Release notes](https://github.com/puppeteer/puppeteer/releases)
- [Commits](https://github.com/puppeteer/puppeteer/compare/v5.4.0...v5.4.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-28 09:56:18 -04:00
dependabot[bot] 1d6beb8235 chore(deps-dev): bump webpack from 5.2.0 to 5.3.0 (#616)
Bumps [webpack](https://github.com/webpack/webpack) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.2.0...v5.3.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-28 09:56:06 -04:00
dependabot[bot] bde6a25063 chore(deps): bump mqtt from 4.2.1 to 4.2.3 (#615)
Bumps [mqtt](https://github.com/mqttjs/MQTT.js) from 4.2.1 to 4.2.3.
- [Release notes](https://github.com/mqttjs/MQTT.js/releases)
- [Commits](https://github.com/mqttjs/MQTT.js/compare/v4.2.1...v4.2.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-28 09:55:48 -04:00
elvador 273d058414 feat: add series to Discord webhook (#618) 2020-10-28 09:45:46 -04:00
Jef LeCompte 8548c07e3b docs: update pr template 2020-10-28 01:00:11 -04:00
Jef LeCompte fa90afc42c chore(store): add 3070 to best buy (#612) 2020-10-27 12:39:41 -04:00
dependabot[bot] ceb049b317 chore(deps-dev): bump @types/node from 14.14.3 to 14.14.5 (#609)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.3 to 14.14.5.
- [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-10-27 10:19:13 -04:00
dependabot[bot] 2213c9e140 chore(deps-dev): bump typescript from 4.0.3 to 4.0.5 (#610)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.0.3 to 4.0.5.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.0.3...v4.0.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-27 10:19:04 -04:00
designgears 3b5b2b1182 docs: add origin country to non-US stores (#608) 2020-10-26 11:49:49 -04:00
Mark Dietzer 12dd49c387 chore(web): prettify (#607) 2020-10-26 11:32:38 -04:00
Jef LeCompte 0318f33234 chore: remove discord 2020-10-26 11:31:09 -04:00
Álvaro Baquedano Simón c1dda4f987 feat(store): support for coolmod (#605)
Co-authored-by: Álvaro Baquedano Simon <alvarobasi@hotmail.com>
Co-authored-by: Álvaro Baquedano Simón <alvarobasi@laposte.net>
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-26 11:21:09 -04:00
Mark Dietzer 61a23fe978 fix: update new additions with web panel and sleep operations (#606) 2020-10-26 11:20:27 -04:00
dependabot[bot] 2f57632479 chore(deps): bump dorny/paths-filter from v2.5.0 to v2.5.1 (#602)
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from v2.5.0 to v2.5.1.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/v2.5.0...b37d4e9e86f9ae7facbf4a3cc58d98e7139cfc31)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-26 11:16:32 -04:00
dependabot[bot] 17555ece11 chore(deps-dev): bump @types/node from 14.14.2 to 14.14.3 (#603)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.2 to 14.14.3.
- [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-10-26 11:16:21 -04:00
dependabot[bot] 16adc3efda chore(deps): bump puppeteer from 5.3.1 to 5.4.0 (#604)
Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/puppeteer/puppeteer/releases)
- [Commits](https://github.com/puppeteer/puppeteer/compare/v5.3.1...v5.4.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-26 11:16:10 -04:00
Jef LeCompte 3c22a458b2 chore: merge 2020-10-25 09:38:56 -04:00
Jef LeCompte 3c111bd3a8 chore: audit fix
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-25 09:38:34 -04:00
dependabot[bot] 0a633ddcfb chore(deps): bump twitch-chat-client from 4.2.6 to 4.2.7 (#581)
Bumps [twitch-chat-client](https://github.com/d-fischer/twitch/tree/HEAD/packages/twitch-chat-client) from 4.2.6 to 4.2.7.
- [Release notes](https://github.com/d-fischer/twitch/releases)
- [Commits](https://github.com/d-fischer/twitch/commits/v4.2.7/packages/twitch-chat-client)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-25 09:37:22 -04:00
dependabot[bot] 0b53cb5e51 chore(deps): bump node from 14.14.0-alpine3.12 to 15.0.1-alpine3.12 (#599)
Bumps node from 14.14.0-alpine3.12 to 15.0.1-alpine3.12.

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-25 09:37:11 -04:00
dependabot[bot] 5cfb1a62d7 chore(deps): bump amannn/action-semantic-pull-request from v2.0.0 to v2.1.0 (#567)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-25 09:36:59 -04:00
Mark Dietzer 373d1a9738 feat(api): add rudimentary web control panel (#183) 2020-10-25 09:36:02 -04:00
dependabot[bot] 83f82d66d9 chore(deps-dev): bump nodemon from 2.0.5 to 2.0.6 (#548)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-25 00:03:43 -04:00
dependabot[bot] 5a3bb437aa chore(deps-dev): bump webpack from 5.1.3 to 5.2.0 (#583)
Bumps [webpack](https://github.com/webpack/webpack) from 5.1.3 to 5.2.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.1.3...v5.2.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-25 00:02:24 -04:00
dependabot[bot] e8b96eb649 chore(deps): bump twitch from 4.2.6 to 4.2.7 (#582)
Bumps [twitch](https://github.com/d-fischer/twitch/tree/HEAD/packages/twitch) from 4.2.6 to 4.2.7.
- [Release notes](https://github.com/d-fischer/twitch/releases)
- [Commits](https://github.com/d-fischer/twitch/commits/v4.2.7/packages/twitch)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-25 00:02:13 -04:00
SesioN a42418f814 feat: allow filtering per model by a specific series (#595)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-25 00:01:55 -04:00
PaddseL 699de573f6 feat(store): add new cards to german web shops (#597)
Co-authored-by: fubu2k <48277447+fubu2k@users.noreply.github.com>
2020-10-24 23:53:14 -04:00
SesioN a2af30b703 fix: page sleep (#586)
Fixes #594 

Ref: #576
2020-10-24 08:15:00 -04:00
Dennis Markmann 2e5db7af04 feat(store): add founders editions to notebooksbilliger.de (#593) 2020-10-24 08:13:09 -04:00
xninjax f9c4c25874 feat(store): add max price containers to multiple stores (#579) 2020-10-23 08:06:55 -04:00
SesioN 503d76f3c7 feat: optional per store min and max page sleep time (#576)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-22 23:03:58 -04:00
Jingsi Zhu 11ee0bf1a3 feat: Add PagerDuty Integration (#565)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-22 20:48:37 -04:00
Jack Webb ac37f87793 docs: readme install instructions typo (#574) 2020-10-22 18:13:52 -04:00
rust84 4847725d3a feat(store): add ttl for scan (#555)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-22 18:13:10 -04:00
fatmoo5 f6db3489b8 fix: asus store links (#573) 2020-10-22 15:31:38 -04:00
dependabot[bot] 9de2307c64 chore(deps): bump twitch-auth from 4.2.6 to 4.2.7 (#566)
Bumps [twitch-auth](https://github.com/d-fischer/twitch/tree/HEAD/packages/twitch-auth) from 4.2.6 to 4.2.7.
- [Release notes](https://github.com/d-fischer/twitch/releases)
- [Commits](https://github.com/d-fischer/twitch/commits/v4.2.7/packages/twitch-auth)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-22 09:12:11 -04:00
dependabot[bot] 6203a1c18b chore(deps-dev): bump @types/node from 14.11.10 to 14.14.2 (#568)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-22 08:58:44 -04:00
Álvaro Baquedano Simón 699e77d960 fix(store): pccomponents model normalization (#563)
Co-authored-by: Álvaro Baquedano Simon <alvarobasi@hotmail.com>
2020-10-22 08:26:44 -04:00
dependabot[bot] 1f095ce6ac chore(deps): bump @slack/web-api from 5.12.0 to 5.13.0 (#569)
Bumps [@slack/web-api](https://github.com/slackapi/node-slack-sdk) from 5.12.0 to 5.13.0.
- [Release notes](https://github.com/slackapi/node-slack-sdk/releases)
- [Commits](https://github.com/slackapi/node-slack-sdk/compare/@slack/web-api@5.12.0...@slack/web-api@5.13.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-22 08:14:44 -04:00
PaddseL 675f13abb8 feat: twitch chat notification, multiple telegram chat ids and german web shop overhaul (#528) 2020-10-21 09:51:05 -04:00
DisguisedOtter 58416e1994 feat(store): support for azerty (#557) 2020-10-20 19:32:04 -04:00
Álvaro Baquedano Simón 062201f092 feat(store): Add PCComponentes and Amazon-ES Stores. (#558)
Co-authored-by: Álvaro Baquedano <alvarobasi@hotmail.com>
Co-authored-by: Álvaro Baquedano Simón <alvarobasi@laposte.net>
2020-10-20 19:31:22 -04:00
DisguisedOtter aca6523333 feat: add alternate.nl support (#552) 2020-10-20 14:04:59 -04:00
SesioN 10a81dcbd5 feat: add support for multiple browser user agents (#547)
Co-authored-by: Pawel Grzesik <p.grzesik@francotyp.com>
2020-10-20 14:03:38 -04:00
xninjax 68ff5bf836 feat: mqtt alerts (#538) 2020-10-20 12:16:33 -04:00
Kazan 4fefe22a90 fix: update gigabyte model vision oc (#550)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-20 12:15:32 -04:00
GRINSPANKER13 2b6457e840 feat: add incognito mode (#534)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-20 12:07:08 -04:00
Farsab Nazir Butt 5ea7cc4ca4 fix: add support for stores label text to have uppercase letters (#526) 2020-10-19 11:19:16 -04:00
dependabot[bot] 542b92301a chore(deps-dev): bump webpack from 5.1.0 to 5.1.3 (#542)
Bumps [webpack](https://github.com/webpack/webpack) from 5.1.0 to 5.1.3.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.1.0...v5.1.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 11:15:54 -04:00
dependabot[bot] 0c8771e3eb chore(deps): bump dorny/paths-filter from v2 to v2.5.0 (#541)
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from v2 to v2.5.0.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/v2...189a1963db079e08671341321aabb27677fb0ab6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 11:15:47 -04:00
dependabot[bot] c82b986aab chore(deps-dev): bump @types/node from 14.11.8 to 14.11.10 (#540)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.11.8 to 14.11.10.
- [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-10-19 11:15:39 -04:00
dependabot[bot] 92c5a02f30 chore(deps): bump node from 14.13.1-alpine3.12 to 14.14.0-alpine3.12 (#539)
Bumps node from 14.13.1-alpine3.12 to 14.14.0-alpine3.12.

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 11:15:27 -04:00
dependabot[bot] d575b8b96a chore(deps-dev): bump nodemon from 2.0.4 to 2.0.5 (#518)
Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v2.0.4...v2.0.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-15 08:52:47 -04:00
dependabot[bot] 8e7b83a55a chore(deps): bump nodemailer from 6.4.13 to 6.4.14 (#519)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.4.13 to 6.4.14.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.4.13...v6.4.14)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-15 08:52:35 -04:00
dependabot[bot] 3b815a9f24 chore(deps): bump twilio from 3.49.4 to 3.50.0 (#523)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-15 08:52:07 -04:00
dependabot[bot] 0263bf8b12 chore(deps-dev): bump webpack from 5.0.0 to 5.1.0 (#520)
Bumps [webpack](https://github.com/webpack/webpack) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.0.0...v5.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-15 08:51:56 -04:00
dependabot[bot] 199fac45ea chore(deps): bump GoogleCloudPlatform/release-please-action from v2.5.5 to v2.5.6 (#522)
Bumps [GoogleCloudPlatform/release-please-action](https://github.com/GoogleCloudPlatform/release-please-action) from v2.5.5 to v2.5.6.
- [Release notes](https://github.com/GoogleCloudPlatform/release-please-action/releases)
- [Changelog](https://github.com/GoogleCloudPlatform/release-please-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GoogleCloudPlatform/release-please-action/compare/v2.5.5...0a415f5e4668f50bf4df66a66627839b4a96b377)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-15 08:51:42 -04:00
Chloe Isabelle dc2fcf5ab4 feat: add proshop-dk (#524) 2020-10-15 08:50:27 -04:00
Chloe Isabelle 1aaa102c06 feat: add caseking and proshop-de store (#521) 2020-10-14 11:32:30 -04:00
xninjax 32f114b235 fix: max price getter updated to match .env (#516)
Fixes #514, resolves #510
2020-10-13 17:30:26 -04:00
Jef LeCompte 053b22100b chore: update versioning
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-13 13:38:04 -04:00
fubu2k b319bc1080 chore: more links for mediamarket (#493)
updated with some 3090 cards
2020-10-13 13:28:50 -04:00
fubu2k e0735903b7 fix: typo in maxPrice for 3090 (#496) 2020-10-13 13:27:24 -04:00
Feiko Wielsma 586029c92a fix: dutch stores components (#513)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
Co-authored-by: Feiko Wielsma <f.wielsma@opnt.nl>
2020-10-13 13:20:25 -04:00
Steffen 1f89945c57 feat: clear cookies and cache (#515)
Fixes #417
2020-10-13 13:19:48 -04:00
dependabot[bot] f24f56549e chore(deps-dev): bump webpack from 4.44.2 to 5.0.0 (#503)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-12 12:29:54 -04:00
xninjax 207bc411ed feat(store): add rog-strix-3080 to newegg-ca (#489) 2020-10-12 12:29:17 -04:00
dependabot[bot] 1efde6b783 chore(deps): bump GoogleCloudPlatform/release-please-action from v2.5.3 to v2.5.5 (#502)
Bumps [GoogleCloudPlatform/release-please-action](https://github.com/GoogleCloudPlatform/release-please-action) from v2.5.3 to v2.5.5.
- [Release notes](https://github.com/GoogleCloudPlatform/release-please-action/releases)
- [Changelog](https://github.com/GoogleCloudPlatform/release-please-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GoogleCloudPlatform/release-please-action/compare/v2.5.3...7332b997483bf339ae2aadcfdaa0e0f569ed36d5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-12 12:05:41 -04:00
dependabot[bot] 51d8b5290a chore(deps-dev): bump @types/node from 14.11.7 to 14.11.8 (#504)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.11.7 to 14.11.8.
- [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-10-12 12:03:04 -04:00
Raymond Ortiz 2b0eab656d feat: allow multiple microcenter locations (#487) 2020-10-12 00:02:19 -04:00
admon84 cbc3b0b025 fix: bestbuy label container selector precision (#491) 2020-10-11 23:58:27 -04:00
admon84 81daa7c559 fix: updating amazon test model card to an in stock product (#492) 2020-10-11 23:57:40 -04:00
Jef LeCompte 97357c19b8 chore: bump node to 14.13.1
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-11 23:18:57 -04:00
er1992 f7ed86506b fix: trim strings from comma-separated values (#472)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-10 12:12:23 -04:00
Andrew Mackrodt 0cdf6657bf chore: add typescript definitions for custom types (#486) 2020-10-10 12:06:38 -04:00
Feiko Wielsma 5c61333df0 feat: add coolblue store (#482)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
Co-authored-by: Feiko Wielsma <f.wielsma@opnt.nl>
2020-10-10 11:59:03 -04:00
mckay17 e5909c532d feat: add rog strix oc to amazon-de (#471) 2020-10-09 18:28:40 -04:00
Jef LeCompte a04a0f34af docs: fix devin to denver
Part of #442

Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-09 17:23:15 -05:00
Jef LeCompte 79b9bfe9a8 revert: reduce false-positive during ci/cd
Refs: 4192b71
2020-10-09 16:54:21 -05:00
Jef LeCompte 4192b7108e chore(pipeline): reduce false-positive during ci/cd
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-09 16:51:57 -05:00
Jef LeCompte e7592db963 chore: add missing env
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-09 16:48:10 -05:00
VictorV c857985a6d feat(docker): add docker and publish images to ghcr (#411)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-09 17:42:29 -04:00
dependabot[bot] e1cae3ed71 chore(deps): bump GoogleCloudPlatform/release-please-action from v2.4.2 to v2.5.3 (#480)
Bumps [GoogleCloudPlatform/release-please-action](https://github.com/GoogleCloudPlatform/release-please-action) from v2.4.2 to v2.5.3.
- [Release notes](https://github.com/GoogleCloudPlatform/release-please-action/releases)
- [Changelog](https://github.com/GoogleCloudPlatform/release-please-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GoogleCloudPlatform/release-please-action/compare/v2.4.2...c05cf8bb4fd8bcaa85121b7b7a024e57d97072b4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-09 16:17:46 -04:00
dependabot[bot] 92696d4654 chore(deps-dev): bump @types/node from 14.11.5 to 14.11.7 (#481)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.11.5 to 14.11.7.
- [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-10-09 16:17:18 -04:00
Jef LeCompte ed970fc397 fix: cyclical dependencies
Fixes #468

Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-09 13:29:41 -05:00
Jef LeCompte eac05c610d docs: add bandwidth note
Resolves #479
2020-10-09 13:59:07 -04:00
dependabot[bot] 6b9d8cd006 chore(deps-dev): bump @types/node from 14.11.2 to 14.11.5 (#460)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.11.2 to 14.11.5.
- [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-10-06 22:41:09 -04:00
Jef LeCompte b43f352707 chore: reduce complexity of min/max
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-06 22:39:22 -04:00
Jef LeCompte 812093455e chore: add pr title lint
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-06 22:34:55 -04:00
xninjax 7274ead169 bugfix: env min/max input validation (#465) 2020-10-06 22:21:21 -04:00
Jef LeCompte 86bef5a617 chore: refactor max price for series
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-06 22:03:19 -04:00
jastheace 8adc07a03e feat: max price per series (#451) 2020-10-06 21:51:32 -04:00
adrian549092 160ae37d7b feat: add support for specifying smtp server (#458)
Co-authored-by: Adrian Martin <adrian.martin@nbcuni.com>
2020-10-06 21:46:32 -04:00
Georges Sabbagh 332b4a8246 feat: add link series to error message for better context (#265) 2020-10-06 21:45:03 -04:00
tconex 85a07dc0dc feat: add german stores (#462)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-06 21:43:48 -04:00
Andrew Mackrodt b9b6b55c29 feat: add uk stores (#455) 2020-10-06 11:35:54 -04:00
dependabot[bot] ee8cb12156 chore(deps): bump nodemailer from 6.4.12 to 6.4.13 (#453)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.4.12 to 6.4.13.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.4.12...v6.4.13)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-05 11:40:05 -04:00
Andrew Mackrodt 4157a5d746 chore(store): normalize model names (#444) 2020-10-04 14:44:11 -04:00
Jef LeCompte 4ea417cfc1 docs: note about web with microcenter 2020-10-04 14:40:07 -04:00
undaunt 5109227e88 feat(store): add ftw3 ultra to microcenter (#448) 2020-10-04 14:27:14 -04:00
Jef LeCompte ae6bc86bcb feat: add meta to logger (#437) 2020-10-03 13:20:32 -04:00
Jef LeCompte 8098a31092 chore: refactor config and fix audits (#406) 2020-10-03 13:15:39 -04:00
Logan Saso f006a8595b chore: update nvidia-api test with shield tv (#432) 2020-10-03 13:02:43 -04:00
Jef LeCompte 8ef8109830 docs: fix attgo 2020-10-03 12:32:08 -04:00
Julio Valerio eb9d082d17 feat(notification): add at&t prepaid carrier (#425) 2020-10-03 12:31:39 -04:00
Julio Valerio fd3351c028 docs: add at&t prepaid support (#427) 2020-10-03 12:30:26 -04:00
xninjax b7e5941a95 fix(config): MAX_PRICE quotations (#426) 2020-10-03 12:29:05 -04:00
pntless 9d5272a0a5 chore(store): update aorus master 3080 links (#431) 2020-10-03 12:27:35 -04:00
Josh Eckerman 628cab1b60 fix(store): update inStock and maxPrice for newegg, newegg-ca (#433)
Fixes #400

Co-authored-by: Josh Eckerman <josh.eckerman@gubagoo.com>
2020-10-03 12:26:59 -04:00
PaddseL b4d8733d18 feat: add asus-de store and asus 3080 tuf/oc to amazon-de (#436) 2020-10-03 12:25:28 -04:00
Jef LeCompte 0c810d62e2 chore(store): add 3090 strix amazon
Closes #420
2020-10-02 17:01:25 -04:00
pntless 4085136269 feat(store): add models to bestbuy (#421) 2020-10-02 16:50:36 -04:00
xninjax fd294d2baa feat: max price filtering (#383) 2020-10-02 12:59:06 -04:00
dependabot[bot] 02f9ed5e53 chore(deps): bump GoogleCloudPlatform/release-please-action from v2.4.1 to v2.4.2 (#415)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-02 12:55:25 -04:00
dependabot[bot] 177203a085 chore(deps): bump actions/setup-node from v2.1.1 to v2.1.2 (#416)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from v2.1.1 to v2.1.2.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.1.1...c6fd00ceb9747fb23ffdf72987450a2664414867)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-02 12:55:12 -04:00
Jef LeCompte eee6b8183a chore: update issue templates
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-01 13:35:16 -04:00
karmanbadhesha 313d176848 feat(store): add evga 3090 to newegg canada (#396) 2020-10-01 13:23:35 -04:00
pntless fddc0021d3 feat(store): adds aorus master 3080 to newegg (#402) 2020-10-01 13:22:37 -04:00
adolfo bernal 7a981e7456 fix: add status code 429 to bandh backOffStatusCodes (#404) 2020-10-01 13:15:05 -04:00
Jef LeCompte 760d57b113 chore: ignore markdown for cicd 2020-10-01 13:12:59 +00:00
Seán O'Grady 7ce6904887 chore: Add discord url to top menu (#377)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-10-01 09:11:07 -04:00
dependabot[bot] 4b3018345b chore(deps): bump nodemailer from 6.4.11 to 6.4.12 (#399)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.4.11 to 6.4.12.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.4.11...v6.4.12)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-01 08:09:41 -04:00
dependabot[bot] e27ea63674 chore(deps): bump puppeteer-extra-plugin-stealth from 2.6.1 to 2.6.2 (#398)
Bumps [puppeteer-extra-plugin-stealth](https://github.com/berstend/puppeteer-extra) from 2.6.1 to 2.6.2.
- [Release notes](https://github.com/berstend/puppeteer-extra/releases)
- [Commits](https://github.com/berstend/puppeteer-extra/compare/puppeteer-extra-plugin-stealth@2.6.1...puppeteer-extra-plugin-stealth@2.6.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-01 08:09:22 -04:00
Jef LeCompte b8d4f2f83e chore: update .editorconfig
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-30 17:48:14 -04:00
adolfo bernal d3bb507edc fix: gamestop false positives (#395)
Fixed issue where gamestop would generate false positives because the string add to cart was still included even when there was no stock available.  Just added the outOfStock label to look for not available, which is actually displayed on the button.
2020-09-30 16:49:09 -04:00
dependabot[bot] 5a5d03b259 chore(deps): bump twilio from 3.49.3 to 3.49.4 (#375)
Bumps [twilio](https://github.com/twilio/twilio-node) from 3.49.3 to 3.49.4.
- [Release notes](https://github.com/twilio/twilio-node/releases)
- [Changelog](https://github.com/twilio/twilio-node/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-node/compare/3.49.3...3.49.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-30 14:26:49 -04:00
dependabot[bot] 4db4f1f9d7 chore(deps): bump open from 7.2.1 to 7.3.0 (#387)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-30 14:22:27 -04:00
Jef LeCompte f980a0bba0 docs: list cleanups, unnecessary bolding, etc
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-30 12:04:47 -04:00
Jef LeCompte 9e7976df57 fix(store): gamestop label.inStock 2020-09-30 11:23:39 -04:00
Grant 7148451b66 feat(store): add gamestop (#390) 2020-09-30 11:14:42 -04:00
Jef LeCompte fdd92f9d65 docs: add CONTRIBUTING.md and add screenshot 2020-09-30 13:23:44 +00:00
Xyrec 589fbbcd34 fix: amazon.{nl,de} test urls (#345) 2020-09-30 00:44:01 -04:00
Andrew Mackrodt f1d22d1684 fix: remove 3090 drid for DK/FI regions (#361) 2020-09-30 00:43:13 -04:00
pntless d84a409fc7 chore: normalize ftw3 naming (#366) 2020-09-30 00:42:27 -04:00
Jef LeCompte 1a0753e739 docs: positioning 2020-09-30 00:41:14 -04:00
Jef LeCompte b110a5a429 docs: update brand/stores formatting 2020-09-30 00:39:56 -04:00
pntless 54a9890130 docs: add brands and models (#351) 2020-09-30 00:39:26 -04:00
Grant e55398e789 feat(store): add asus strix oc to asus store (#385) 2020-09-29 20:56:16 -04:00
Andrew Mackrodt 3b7487e97a feat: configurable status code behaviours (#340) 2020-09-29 09:54:26 -04:00
Andrew Mackrodt 7d8897cd9f fix: envOrNumber behavior (#364) 2020-09-28 14:07:52 -04:00
Augustin Winther d2476ddb08 fix: corrected norway locales (#356)
norway currency should be `NOK` not `EUR`
norway drLocale should be `no_no` not `nb_no`
2020-09-28 07:47:42 -04:00
Andrew Mackrodt 95c28a8d6d docs: update supported countries (#339)
Removes `russia` from documentation as there are no products
2020-09-27 21:49:22 -04:00
adrian549092 fdcd787f91 feat: support for proxy server (#352)
Co-authored-by: Adrian Martin <adrian.martin@nbcuni.com>
2020-09-27 21:43:58 -04:00
Jef LeCompte 772de900a1 fix(store): container names on nvidia (#333) 2020-09-27 21:40:23 -04:00
admon84 5b8d774b7c feat(store): nvidia debug card for "product details" page (#337) 2020-09-27 18:00:03 -04:00
Seán O'Grady 5414b249a6 fix(notifications): twilio client creation (#349) 2020-09-27 17:23:48 -04:00
Wills Bithrey f2f8d81498 feat(notification): add Twillio notification provider (#344) 2020-09-27 15:12:56 -04:00
Andrew Mackrodt 3b90bbbe5d fix(notification): discord false triggers (#346) 2020-09-27 15:01:08 -04:00
Andrew Mackrodt 1bac1b928d feat: retry logic for nvidia session token and adding to cart (#347) 2020-09-27 14:59:17 -04:00
Andrew Mackrodt 3bde805f2c chore: add missing nvidia drid for all series/regions (#330) 2020-09-26 16:02:41 -04:00
Brian Baraban 8828dd15cd feat(notification): add to field for email (#327) 2020-09-26 15:52:57 -04:00
Jef LeCompte 07bd246e87 fix: in stock wait time (#325)
Fixes #315

Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-26 08:48:07 -04:00
Jef LeCompte 24786a443a chore: reuse variable
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-26 08:43:50 -04:00
ththiem 03755d5eb1 fix(notification): sms subject output (#298) 2020-09-26 08:28:46 -04:00
Joshua Higgins 0aa7ab596c feat: low bandwidth mode (#294)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-26 08:05:02 -04:00
niklashjh eda6c85fc0 feat: add norway to nvidia-api (#304)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 19:46:17 -04:00
Loc Mai 696b75dfbd chore: add .exe to .gitignore (#289) 2020-09-25 19:42:15 -04:00
Joshua Higgins f6760d3c65 feat(store): add pny (#295) 2020-09-25 19:37:43 -04:00
Jef LeCompte 9636572c7d fix(env): default LOG_LEVEL
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 19:33:38 -04:00
Jef LeCompte 1bead4712b chore(store): add sweden 3090
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 19:31:59 -04:00
George a2983eb54c fix: add amazon-nl, captcha amazon-de, add ftw3 newegg (#293) 2020-09-25 19:30:00 -04:00
Chris Bandrowsky 349f55d360 fix: denver microcenter name (#296) 2020-09-25 19:26:46 -04:00
Andrew Mackrodt 38ee437cc9 chore: remove nvidiaLocale (#318) 2020-09-25 19:08:47 -04:00
Andrew Mackrodt 8d8e5b587d fix: nvidia-api (#314) 2020-09-25 19:00:12 -04:00
Jef LeCompte d9dea3d0f2 chore: change dev task to start:dev
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 18:30:05 -04:00
Andrew Mackrodt b868d1a483 feat: enhanced lookup behaviour (#270)
Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 18:29:10 -04:00
Jef LeCompte 7ef9d935c6 docs: update IN_STOCK_WAIT_TIME
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 08:27:26 -04:00
Jef LeCompte c7a716f981 feat: in stock wait time per link now
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-25 08:25:58 -04:00
Joshua Higgins d25a643425 feat(store): add additional cards to all stores (#286) 2020-09-24 15:28:45 -04:00
Joshua Higgins d69189f12c feat(store): add amazon cards and cartUrls (#284) 2020-09-24 14:50:53 -04:00
Jef LeCompte 2893f76e29 chore: remove patreon 2020-09-24 14:31:16 -04:00
serg06 e992cf4db8 feat(store): add 3090s to amazon-ca (#274) 2020-09-24 14:22:14 -04:00
Jef LeCompte 4c63acc75a docs: fix details tag 2020-09-24 09:55:56 -04:00
Jef LeCompte 21aa700a86 docs: add microcenter stores 2020-09-24 13:55:10 +00:00
Jef LeCompte 8165033ea4 chore(store): fix microcenter name 2020-09-24 08:04:31 -04:00
Jef LeCompte 62172d224a Merge branch 'main' of github.com:jef/nvidia-snatcher into main 2020-09-23 23:49:13 -04:00
Jef LeCompte 9d3b7d38ab chore(store): add more nvidia links
Signed-off-by: Jef LeCompte <jeffreylec@gmail.com>
2020-09-23 23:49:05 -04:00
John Dyer f277172191 feat(notification): add pushover priority setting (#186) 2020-09-23 23:25:42 -04:00
105 changed files with 13572 additions and 1844 deletions
+6
View File
@@ -0,0 +1,6 @@
.idea/
.git/
.vs/
.vscode/
build/
node_modules/
+12 -12
View File
@@ -1,12 +1,12 @@
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space
indent_size = 2
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{yml,json}]
indent_style = space
indent_size = 2
+69 -35
View File
@@ -2,38 +2,72 @@
# Read https://github.com/jef/nvidia-snatcher#customization for help on customizing this file
#############################################################################################
BROWSER_TRUSTED=""
DISCORD_NOTIFY_GROUP=""
DISCORD_WEB_HOOK=""
EMAIL_USERNAME=""
EMAIL_PASSWORD=""
HEADLESS=""
IN_STOCK_WAIT_TIME=""
LOG_LEVEL=""
MICROCENTER_LOCATION=""
OPEN_BROWSER=""
PAGE_TIMEOUT=""
PHONE_NUMBER=""
PHONE_CARRIER=""
PLAY_SOUND=""
PUSHBULLET=""
PUSHOVER_TOKEN=""
PUSHOVER_USER=""
PAGE_SLEEP_MIN=""
PAGE_SLEEP_MAX=""
SHOW_ONLY_BRANDS=""
SHOW_ONLY_MODELS=""
SHOW_ONLY_SERIES=""
SLACK_CHANNEL=""
SLACK_TOKEN=""
STORES=""
COUNTRY=""
SCREENSHOT="false"
TELEGRAM_ACCESS_TOKEN=""
TELEGRAM_CHAT_ID=""
TWITTER_CONSUMER_KEY=""
TWITTER_CONSUMER_SECRET=""
TWITTER_ACCESS_TOKEN_KEY=""
TWITTER_ACCESS_TOKEN_SECRET=""
TWITTER_TWEET_TAGS=""
USER_AGENT=""
BROWSER_TRUSTED=
COUNTRY=
DESKTOP_NOTIFICATIONS=
DISCORD_NOTIFY_GROUP=
DISCORD_WEB_HOOK=
EMAIL_PASSWORD=
EMAIL_TO=
EMAIL_USERNAME=
HEADLESS=
IN_STOCK_WAIT_TIME=
LOG_LEVEL=
LOW_BANDWIDTH=
MAX_PRICE_SERIES_3070=
MAX_PRICE_SERIES_3080=
MAX_PRICE_SERIES_3090=
MICROCENTER_LOCATION=
MQTT_BROKER_ADDRESS=
MQTT_BROKER_PORT=
MQTT_CLIENT_ID=
MQTT_PASSWORD=
MQTT_QOS=
MQTT_TOPIC=
MQTT_USERNAME=
NVIDIA_ADD_TO_CART_ATTEMPTS=
NVIDIA_SESSION_TTL=
OPEN_BROWSER=
PAGE_BACKOFF_MIN=
PAGE_BACKOFF_MAX=
PAGE_SLEEP_MIN=
PAGE_SLEEP_MAX=
PAGE_TIMEOUT=
PAGERDUTY_INTEGRATION_KEY=
PAGERDUTY_SEVERITY=
PHONE_CARRIER=
PHONE_NUMBER=
PLAY_SOUND=
PROXY_ADDRESS=
PROXY_PORT=
PUSHBULLET=
PUSHOVER_TOKEN=
PUSHOVER_USER=
PUSHOVER_PRIORITY=
SCREENSHOT=
SHOW_ONLY_BRANDS=
SHOW_ONLY_MODELS=
SHOW_ONLY_SERIES=
SLACK_CHANNEL=
SLACK_TOKEN=
SMTP_ADDRESS=
SMTP_PORT=
STORES=
TELEGRAM_ACCESS_TOKEN=
TELEGRAM_CHAT_ID=
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_FROM_NUMBER=
TWILIO_TO_NUMBER=
TWITCH_ACCESS_TOKEN=
TWITCH_CHANNEL=
TWITCH_CLIENT_ID=
TWITCH_CLIENT_SECRET=
TWITCH_REFRESH_TOKEN=
TWITTER_ACCESS_TOKEN_KEY=
TWITTER_ACCESS_TOKEN_SECRET=
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_TWEET_TAGS=
USER_AGENT=
WEB_PORT=
-1
View File
@@ -1,3 +1,2 @@
github: jef
patreon: hijef
custom: ["https://www.paypal.me/jxf"]
+1 -1
View File
@@ -2,7 +2,7 @@
name: 🐛 Bug report
about: Report a bug for this project
title: ''
labels: 'bug'
labels: 'type: bug'
assignees: jef
---
+1 -1
View File
@@ -2,7 +2,7 @@
name: 🚀 Feature request
about: Suggest a feature for this project
title: ''
labels: 'enhancement'
labels: 'type: enhancement'
assignees: jef
---
-3
View File
@@ -7,9 +7,6 @@
<!-- Fixes #(issue) -->
<!-- Please also include relevant motivation and context. -->
<!-- Feel free to include your Discord tag here and we'll consider making you a ringer! -->
<!-- Continuous improvements may also grant you contributor access. -->
### Testing
<!-- Please describe the tests that you ran to verify your changes. -->
+26 -2
View File
@@ -3,14 +3,38 @@ on:
push:
branches:
- main
paths-ignore:
- '**.md'
jobs:
cd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: GoogleCloudPlatform/release-please-action@v2.4.1
- uses: GoogleCloudPlatform/release-please-action@v2.5.6
id: release
with:
token: ${{ secrets.GITHUB_TOKEN }}
release-type: node
release-type: simple
package-name: nvidia-snatcher
- name: login into github package registry
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
- name: build nightly docker image
if: ${{ ! steps.release.outputs.release_created }}
run: |
docker build \
-t "ghcr.io/${GITHUB_REPOSITORY}:${GITHUB_SHA:0:7}" \
-t "ghcr.io/${GITHUB_REPOSITORY}:nightly" .
- name: publish nightly
if: ${{ ! steps.release.outputs.release_created }}
run: docker push "ghcr.io/${GITHUB_REPOSITORY}"
- name: build latest docker image
if: ${{ steps.release.outputs.release_created }}
run: |
docker build \
-t "ghcr.io/${GITHUB_REPOSITORY}:${TAG_NAME}" \
-t "ghcr.io/${GITHUB_REPOSITORY}:latest" .
env:
TAG_NAME: ${{ steps.release.outputs.tag_name }}
- name: publish latest
if: ${{ steps.release.outputs.release_created }}
run: docker push "ghcr.io/${GITHUB_REPOSITORY}"
+17 -2
View File
@@ -3,12 +3,14 @@ on:
pull_request:
branches:
- main
paths-ignore:
- '**.md'
jobs:
build-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@v2.1.1
- uses: actions/checkout@v2
- uses: actions/setup-node@v2.1.2
with:
node-version: 14
- uses: actions/cache@v2
@@ -23,3 +25,16 @@ jobs:
npm ci
npm run build
npm run lint
build-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dorny/paths-filter@v2.5.1
id: filter
with:
filters: |
docker:
- 'Dockerfile'
- name: Build image
if: steps.filter.outputs.docker == 'true'
run: docker build .
+15
View File
@@ -0,0 +1,15 @@
name: pr-lint
on:
pull_request_target:
types:
- opened
- edited
- reopened
- synchronize
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v2.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -2
View File
@@ -4,9 +4,15 @@
build/
node_modules/
.env
.env*
.*env
!.env-example
success-*.png
*.wav
*.mp3
*.flac
*.flac
*.exe
desktop.ini
twitch.json
+1 -1
View File
@@ -1 +1 @@
14.11.0
15.0.1
+102
View File
@@ -1,5 +1,107 @@
# Changelog
## [1.6.0](https://www.github.com/jef/nvidia-snatcher/compare/v1.5.0...v1.6.0) (2020-10-29)
### Features
* add series to Discord webhook ([#618](https://www.github.com/jef/nvidia-snatcher/issues/618)) ([273d058](https://www.github.com/jef/nvidia-snatcher/commit/273d05841451ed896f1d505bcd24f2230a143d3a))
* **api:** add rudimentary web control panel ([#183](https://www.github.com/jef/nvidia-snatcher/issues/183)) ([373d1a9](https://www.github.com/jef/nvidia-snatcher/commit/373d1a973865b14b5753517aa70eabf85b136d94))
* **store:** add founders editions to notebooksbilliger.de ([#593](https://www.github.com/jef/nvidia-snatcher/issues/593)) ([2e5db7a](https://www.github.com/jef/nvidia-snatcher/commit/2e5db7af04e6ce79660477d8deb6636ec2e7845e))
* **store:** add max price containers to multiple stores ([#579](https://www.github.com/jef/nvidia-snatcher/issues/579)) ([f9c4c25](https://www.github.com/jef/nvidia-snatcher/commit/f9c4c258748ffd3735f1e7385bde15b6112a1ca7))
* **store:** add new cards to german web shops ([#597](https://www.github.com/jef/nvidia-snatcher/issues/597)) ([699de57](https://www.github.com/jef/nvidia-snatcher/commit/699de573f6579b59ba2c65cc2bf4dd6ccba3c863))
* **store:** Add PCComponentes and Amazon-ES Stores. ([#558](https://www.github.com/jef/nvidia-snatcher/issues/558)) ([062201f](https://www.github.com/jef/nvidia-snatcher/commit/062201f092c7a271202f27e7297b8eb9d7269c02))
* **store:** support for azerty ([#557](https://www.github.com/jef/nvidia-snatcher/issues/557)) ([58416e1](https://www.github.com/jef/nvidia-snatcher/commit/58416e1994006cc2e4055937c675e0f9191f6339))
* **store:** support for coolmod ([#605](https://www.github.com/jef/nvidia-snatcher/issues/605)) ([c1dda4f](https://www.github.com/jef/nvidia-snatcher/commit/c1dda4f9871988fad43d79f3bfe65f7c7860b9f7))
* add `meta` to logger ([#437](https://www.github.com/jef/nvidia-snatcher/issues/437)) ([ae6bc86](https://www.github.com/jef/nvidia-snatcher/commit/ae6bc86bcb75c154a2a68adda324f34f18281700))
* add alternate.nl support ([#552](https://www.github.com/jef/nvidia-snatcher/issues/552)) ([aca6523](https://www.github.com/jef/nvidia-snatcher/commit/aca65233339ddbbcc1dbd06f9011d6d44c6650cf))
* add asus-de store and asus 3080 tuf/oc to amazon-de ([#436](https://www.github.com/jef/nvidia-snatcher/issues/436)) ([b4d8733](https://www.github.com/jef/nvidia-snatcher/commit/b4d8733d1823e2fa5d3a1b80d2d4218bfa737cd7))
* add caseking and proshop-de store ([#521](https://www.github.com/jef/nvidia-snatcher/issues/521)) ([1aaa102](https://www.github.com/jef/nvidia-snatcher/commit/1aaa102c06281b60da593d04ced01f8b20a37a3b))
* add coolblue store ([#482](https://www.github.com/jef/nvidia-snatcher/issues/482)) ([5c61333](https://www.github.com/jef/nvidia-snatcher/commit/5c61333df00c0c30f04e4aced01a498c5816ad58))
* add german stores ([#462](https://www.github.com/jef/nvidia-snatcher/issues/462)) ([85a07dc](https://www.github.com/jef/nvidia-snatcher/commit/85a07dc0dc6a8dfe2aba1d4ea3e4cde6ec083086))
* add incognito mode ([#534](https://www.github.com/jef/nvidia-snatcher/issues/534)) ([2b6457e](https://www.github.com/jef/nvidia-snatcher/commit/2b6457e8409658b843204fa0aeafb00d48f9e53e))
* add link series to error message for better context ([#265](https://www.github.com/jef/nvidia-snatcher/issues/265)) ([332b4a8](https://www.github.com/jef/nvidia-snatcher/commit/332b4a8246320e458729c0b58c310d290df12530))
* Add PagerDuty Integration ([#565](https://www.github.com/jef/nvidia-snatcher/issues/565)) ([11ee0bf](https://www.github.com/jef/nvidia-snatcher/commit/11ee0bf1a3e2d0d550d2731f9de9e178c382ea1f))
* add proshop-dk ([#524](https://www.github.com/jef/nvidia-snatcher/issues/524)) ([dc2fcf5](https://www.github.com/jef/nvidia-snatcher/commit/dc2fcf5ab4dba8be459bd5aa6a41e3e097205d45))
* add support for multiple browser user agents ([#547](https://www.github.com/jef/nvidia-snatcher/issues/547)) ([10a81dc](https://www.github.com/jef/nvidia-snatcher/commit/10a81dcbd54557652537cb641cbbd727d39e34e5))
* add uk stores ([#455](https://www.github.com/jef/nvidia-snatcher/issues/455)) ([b9b6b55](https://www.github.com/jef/nvidia-snatcher/commit/b9b6b55c29d11f48b683816e5b8c1cab127ed5fd))
* allow filtering per model by a specific series ([#595](https://www.github.com/jef/nvidia-snatcher/issues/595)) ([a42418f](https://www.github.com/jef/nvidia-snatcher/commit/a42418f814dcd91354c136288d4c6a24e03a168d))
* optional per store min and max page sleep time ([#576](https://www.github.com/jef/nvidia-snatcher/issues/576)) ([503d76f](https://www.github.com/jef/nvidia-snatcher/commit/503d76f3c7d01368fc978af4ab02924d3d036ac0))
* **notification:** add `to` field for email ([#327](https://www.github.com/jef/nvidia-snatcher/issues/327)) ([8828dd1](https://www.github.com/jef/nvidia-snatcher/commit/8828dd15cd08959cd434bd256e8eac474dd17c49))
* **store:** add ttl for `scan` ([#555](https://www.github.com/jef/nvidia-snatcher/issues/555)) ([4847725](https://www.github.com/jef/nvidia-snatcher/commit/4847725d3a54d2f4dd0c349ff0ad80eb4bc4e9e3))
* add rog strix oc to amazon-de ([#471](https://www.github.com/jef/nvidia-snatcher/issues/471)) ([e5909c5](https://www.github.com/jef/nvidia-snatcher/commit/e5909c532da1c8b731e9835bffd8f31b76550d4d))
* add support for specifying smtp server ([#458](https://www.github.com/jef/nvidia-snatcher/issues/458)) ([160ae37](https://www.github.com/jef/nvidia-snatcher/commit/160ae37d7bee4f0323cb939566d586f1300aece6))
* allow multiple microcenter locations ([#487](https://www.github.com/jef/nvidia-snatcher/issues/487)) ([2b0eab6](https://www.github.com/jef/nvidia-snatcher/commit/2b0eab656d81314ce7d05064662ec138407550c3))
* clear cookies and cache ([#515](https://www.github.com/jef/nvidia-snatcher/issues/515)) ([1f89945](https://www.github.com/jef/nvidia-snatcher/commit/1f89945c5746befb2131381a358efdf8ac530e0c)), closes [#417](https://www.github.com/jef/nvidia-snatcher/issues/417)
* max price per series ([#451](https://www.github.com/jef/nvidia-snatcher/issues/451)) ([8adc07a](https://www.github.com/jef/nvidia-snatcher/commit/8adc07a03e411dd536bebfdc7270db4bbf8ddb34))
* mqtt alerts ([#538](https://www.github.com/jef/nvidia-snatcher/issues/538)) ([68ff5bf](https://www.github.com/jef/nvidia-snatcher/commit/68ff5bf836c63c8e14d02c06777e02e64a6a0f38))
* twitch chat notification, multiple telegram chat ids and german web shop overhaul ([#528](https://www.github.com/jef/nvidia-snatcher/issues/528)) ([675f13a](https://www.github.com/jef/nvidia-snatcher/commit/675f13abb892d1158c92c41e3d57049313a5e0b8))
* **docker:** add docker and publish images to ghcr ([#411](https://www.github.com/jef/nvidia-snatcher/issues/411)) ([c857985](https://www.github.com/jef/nvidia-snatcher/commit/c857985a6d3736287976caf5b173f19046306465))
* **notification:** add at&t prepaid carrier ([#425](https://www.github.com/jef/nvidia-snatcher/issues/425)) ([eb9d082](https://www.github.com/jef/nvidia-snatcher/commit/eb9d082d178a42e35789ba822cfae7b35c0413c1))
* **notification:** add pushover priority setting ([#186](https://www.github.com/jef/nvidia-snatcher/issues/186)) ([f277172](https://www.github.com/jef/nvidia-snatcher/commit/f2771721914a20619833df8ccb2ac44298687b4d))
* **notification:** add Twillio notification provider ([#344](https://www.github.com/jef/nvidia-snatcher/issues/344)) ([f2f8d81](https://www.github.com/jef/nvidia-snatcher/commit/f2f8d81498d1acfb9359f4a690962042ec20d166))
* **store:** add `pny` ([#295](https://www.github.com/jef/nvidia-snatcher/issues/295)) ([f6760d3](https://www.github.com/jef/nvidia-snatcher/commit/f6760d3c65d60eae9e4b1e0fdba34e814f446a4c))
* **store:** add 3090s to amazon-ca ([#274](https://www.github.com/jef/nvidia-snatcher/issues/274)) ([e992cf4](https://www.github.com/jef/nvidia-snatcher/commit/e992cf4db85f045fc8d03c9b93286fb72bad1061))
* **store:** add additional cards to all stores ([#286](https://www.github.com/jef/nvidia-snatcher/issues/286)) ([d25a643](https://www.github.com/jef/nvidia-snatcher/commit/d25a643425020fa3f7cd48972360ede17501afeb))
* **store:** add amazon cards and `cartUrl`s ([#284](https://www.github.com/jef/nvidia-snatcher/issues/284)) ([d69189f](https://www.github.com/jef/nvidia-snatcher/commit/d69189f12c893fb6d88b198d802ff8e36a69bc88))
* **store:** add asus strix oc to asus store ([#385](https://www.github.com/jef/nvidia-snatcher/issues/385)) ([e55398e](https://www.github.com/jef/nvidia-snatcher/commit/e55398e789d52def6e15d1e5e10f56cdf5ea5bea))
* **store:** add evga 3090 to newegg canada ([#396](https://www.github.com/jef/nvidia-snatcher/issues/396)) ([313d176](https://www.github.com/jef/nvidia-snatcher/commit/313d176848a25f183334db8926ac0ec445a2c481))
* **store:** add ftw3 ultra to microcenter ([#448](https://www.github.com/jef/nvidia-snatcher/issues/448)) ([5109227](https://www.github.com/jef/nvidia-snatcher/commit/5109227e8899f57b828b157309a76c397c872559))
* **store:** add gamestop ([#390](https://www.github.com/jef/nvidia-snatcher/issues/390)) ([7148451](https://www.github.com/jef/nvidia-snatcher/commit/7148451b66e97f4f7dcdcc86ce06ba8712211bd5))
* **store:** add models to bestbuy ([#421](https://www.github.com/jef/nvidia-snatcher/issues/421)) ([4085136](https://www.github.com/jef/nvidia-snatcher/commit/4085136269b00f6bc5c8414836de8310dfec4166))
* **store:** add rog-strix-3080 to newegg-ca ([#489](https://www.github.com/jef/nvidia-snatcher/issues/489)) ([207bc41](https://www.github.com/jef/nvidia-snatcher/commit/207bc411ed240cd9150e018b21e735ba08a4d134))
* add `norway` to `nvidia-api` ([#304](https://www.github.com/jef/nvidia-snatcher/issues/304)) ([eda6c85](https://www.github.com/jef/nvidia-snatcher/commit/eda6c85fc03a70c5933308e96c572a480bb6c8a0))
* configurable status code behaviours ([#340](https://www.github.com/jef/nvidia-snatcher/issues/340)) ([3b7487e](https://www.github.com/jef/nvidia-snatcher/commit/3b7487e97ac9d93344403f50153f2de6243b1f0d))
* enhanced lookup behaviour ([#270](https://www.github.com/jef/nvidia-snatcher/issues/270)) ([b868d1a](https://www.github.com/jef/nvidia-snatcher/commit/b868d1a4833a8ec5ac1c79481530d75cd0c4b01e))
* in stock wait time per link now ([c7a716f](https://www.github.com/jef/nvidia-snatcher/commit/c7a716f981976a76afe61a4d985bd6fe4343595b))
* low bandwidth mode ([#294](https://www.github.com/jef/nvidia-snatcher/issues/294)) ([0aa7ab5](https://www.github.com/jef/nvidia-snatcher/commit/0aa7ab596c907ce72c188eb4b1acdee088307437))
* max price filtering ([#383](https://www.github.com/jef/nvidia-snatcher/issues/383)) ([fd294d2](https://www.github.com/jef/nvidia-snatcher/commit/fd294d2baa06a1c0a68852497889a0412dea492e))
* **store:** adds aorus master 3080 to newegg ([#402](https://www.github.com/jef/nvidia-snatcher/issues/402)) ([fddc002](https://www.github.com/jef/nvidia-snatcher/commit/fddc0021d36e4d0a9dacccc546da8260684f0eeb))
* retry logic for nvidia session token and adding to cart ([#347](https://www.github.com/jef/nvidia-snatcher/issues/347)) ([1bac1b9](https://www.github.com/jef/nvidia-snatcher/commit/1bac1b928d401a819698848f3367edf54836b26f))
* support for proxy server ([#352](https://www.github.com/jef/nvidia-snatcher/issues/352)) ([fdcd787](https://www.github.com/jef/nvidia-snatcher/commit/fdcd787f91f26229db23e2291e8922b947007902))
* **store:** nvidia debug card for "product details" page ([#337](https://www.github.com/jef/nvidia-snatcher/issues/337)) ([5b8d774](https://www.github.com/jef/nvidia-snatcher/commit/5b8d774b7c7d31d6ba6fc43be3ea7b16a87d2e49))
### Bug Fixes
* `amazon.{nl,de}` test urls ([#345](https://www.github.com/jef/nvidia-snatcher/issues/345)) ([589fbbc](https://www.github.com/jef/nvidia-snatcher/commit/589fbbcd34393ceb2bd7c0a8ac391c54e14a21be))
* `envOrNumber` behavior ([#364](https://www.github.com/jef/nvidia-snatcher/issues/364)) ([7d8897c](https://www.github.com/jef/nvidia-snatcher/commit/7d8897cd9fb9ae0db796fd85da3f2b9d1a9f73af))
* `nvidia-api` ([#314](https://www.github.com/jef/nvidia-snatcher/issues/314)) ([8d8e5b5](https://www.github.com/jef/nvidia-snatcher/commit/8d8e5b587d2279a95d3e7837a99bea8c5990a477))
* add `amazon-nl`, captcha `amazon-de`, add ftw3 `newegg` ([#293](https://www.github.com/jef/nvidia-snatcher/issues/293)) ([a2983eb](https://www.github.com/jef/nvidia-snatcher/commit/a2983eb54c419ba3a56abf80d316ea136a05e0fa))
* add status code `429` to bandh `backOffStatusCodes` ([#404](https://www.github.com/jef/nvidia-snatcher/issues/404)) ([7a981e7](https://www.github.com/jef/nvidia-snatcher/commit/7a981e745673f2b12d48d7eed71fc34a6e4ba5b2))
* add support for stores label text to have uppercase letters ([#526](https://www.github.com/jef/nvidia-snatcher/issues/526)) ([5ea7cc4](https://www.github.com/jef/nvidia-snatcher/commit/5ea7cc4ca45c506d2b98d4b643b44ca4ede1d7a9))
* asus store links ([#573](https://www.github.com/jef/nvidia-snatcher/issues/573)) ([f6db348](https://www.github.com/jef/nvidia-snatcher/commit/f6db3489b8b8a737dfc425880703928d5bc8916d))
* bestbuy label container selector precision ([#491](https://www.github.com/jef/nvidia-snatcher/issues/491)) ([cbc3b0b](https://www.github.com/jef/nvidia-snatcher/commit/cbc3b0b025469e9882ba8267f18f909c08c6c931))
* corrected norway locales ([#356](https://www.github.com/jef/nvidia-snatcher/issues/356)) ([d2476dd](https://www.github.com/jef/nvidia-snatcher/commit/d2476ddb08606545b32b9676e2d299d57ec5cb6a))
* cyclical dependencies ([ed970fc](https://www.github.com/jef/nvidia-snatcher/commit/ed970fc397210eb6ff684c7e82c79eb685a5d0d9)), closes [#468](https://www.github.com/jef/nvidia-snatcher/issues/468)
* denver `microcenter` name ([#296](https://www.github.com/jef/nvidia-snatcher/issues/296)) ([349f55d](https://www.github.com/jef/nvidia-snatcher/commit/349f55d3603517b5741fce6ac42b29141816ea79))
* dutch stores components ([#513](https://www.github.com/jef/nvidia-snatcher/issues/513)) ([586029c](https://www.github.com/jef/nvidia-snatcher/commit/586029c92aa19bdddcc85b7a837bb7a16bb1d28d))
* gamestop false positives ([#395](https://www.github.com/jef/nvidia-snatcher/issues/395)) ([d3bb507](https://www.github.com/jef/nvidia-snatcher/commit/d3bb507edca112124115ed88fb03cf14440d95ef))
* in stock wait time ([#325](https://www.github.com/jef/nvidia-snatcher/issues/325)) ([07bd246](https://www.github.com/jef/nvidia-snatcher/commit/07bd246e876cd27df1b5019af5ee8613bb5368f0)), closes [#315](https://www.github.com/jef/nvidia-snatcher/issues/315)
* max price getter updated to match .env ([#516](https://www.github.com/jef/nvidia-snatcher/issues/516)) ([32f114b](https://www.github.com/jef/nvidia-snatcher/commit/32f114b23527ee247c84c081a2cd0264de3b4847)), closes [#514](https://www.github.com/jef/nvidia-snatcher/issues/514) [#510](https://www.github.com/jef/nvidia-snatcher/issues/510)
* page sleep ([#586](https://www.github.com/jef/nvidia-snatcher/issues/586)) ([a2af30b](https://www.github.com/jef/nvidia-snatcher/commit/a2af30b70334cdbbfa51140d5de23a2d6b8429c8)), closes [#594](https://www.github.com/jef/nvidia-snatcher/issues/594) [#576](https://www.github.com/jef/nvidia-snatcher/issues/576)
* remove 3090 drid for DK/FI regions ([#361](https://www.github.com/jef/nvidia-snatcher/issues/361)) ([f1d22d1](https://www.github.com/jef/nvidia-snatcher/commit/f1d22d1684c8e70f09acd9978e6ea802d7224c8b))
* trim strings from comma-separated values ([#472](https://www.github.com/jef/nvidia-snatcher/issues/472)) ([f7ed865](https://www.github.com/jef/nvidia-snatcher/commit/f7ed86506b4e76d5bc20787068be83a44ec485b5))
* typo in maxPrice for 3090 ([#496](https://www.github.com/jef/nvidia-snatcher/issues/496)) ([e073590](https://www.github.com/jef/nvidia-snatcher/commit/e0735903b7b4b0d542dbbd76b9a860b1eca24e96))
* update gigabyte model `vision oc` ([#550](https://www.github.com/jef/nvidia-snatcher/issues/550)) ([4fefe22](https://www.github.com/jef/nvidia-snatcher/commit/4fefe22a907094fa39af5b4f2e3f8f3fe6897115))
* update new additions with web panel and sleep operations ([#606](https://www.github.com/jef/nvidia-snatcher/issues/606)) ([61a23fe](https://www.github.com/jef/nvidia-snatcher/commit/61a23fe9782223dc067dbc8251c3b09adbca0a43))
* update pushbullet import to match package ([#637](https://www.github.com/jef/nvidia-snatcher/issues/637)) ([d7360f7](https://www.github.com/jef/nvidia-snatcher/commit/d7360f71ef1a076713246ad2832ca58e7163e492))
* **env:** default `LOG_LEVEL` ([9636572](https://www.github.com/jef/nvidia-snatcher/commit/9636572c7de36f7ac6800ba31ac60fcd7bd2fd03))
* **notification:** discord false triggers ([#346](https://www.github.com/jef/nvidia-snatcher/issues/346)) ([3b90bbb](https://www.github.com/jef/nvidia-snatcher/commit/3b90bbbe5d751003a39823e9113eaee8cbfcf1a2))
* **notification:** sms subject output ([#298](https://www.github.com/jef/nvidia-snatcher/issues/298)) ([03755d5](https://www.github.com/jef/nvidia-snatcher/commit/03755d5eb117ac14797e0180c74f50b401e50cb5))
* **notifications:** twilio client creation ([#349](https://www.github.com/jef/nvidia-snatcher/issues/349)) ([5414b24](https://www.github.com/jef/nvidia-snatcher/commit/5414b249a6f938615cfad02ca22c171a5f86e127))
* **store:** gamestop `label.inStock` ([9e7976d](https://www.github.com/jef/nvidia-snatcher/commit/9e7976df5778a953c4fb6ca7553773655c1f4127))
* **store:** pccomponents model normalization ([#563](https://www.github.com/jef/nvidia-snatcher/issues/563)) ([699e77d](https://www.github.com/jef/nvidia-snatcher/commit/699e77d960b17dcb50378975a1913b0badeabfcc))
* updating amazon test model card to an in stock product ([#492](https://www.github.com/jef/nvidia-snatcher/issues/492)) ([81daa7c](https://www.github.com/jef/nvidia-snatcher/commit/81daa7c5596ab23bd9e6aac29fa63ee09e136827))
* **config:** `MAX_PRICE` quotations ([#426](https://www.github.com/jef/nvidia-snatcher/issues/426)) ([b7e5941](https://www.github.com/jef/nvidia-snatcher/commit/b7e5941a9598a09afabbb79c5636b768345009a3))
* **store:** container names on `nvidia` ([#333](https://www.github.com/jef/nvidia-snatcher/issues/333)) ([772de90](https://www.github.com/jef/nvidia-snatcher/commit/772de900a1386e9635d139e152fe86366404ded4))
* **store:** update `inStock` and `maxPrice` for newegg, newegg-ca ([#433](https://www.github.com/jef/nvidia-snatcher/issues/433)) ([628cab1](https://www.github.com/jef/nvidia-snatcher/commit/628cab1b605e4363c8dd5ad43476292ecb12db74)), closes [#400](https://www.github.com/jef/nvidia-snatcher/issues/400)
### Reverts
* reduce false-positive during ci/cd ([79b9bfe](https://www.github.com/jef/nvidia-snatcher/commit/79b9bfe9a83858117f7a58b4b8b1a1569d80c442))
## [1.5.0](https://www.github.com/jef/nvidia-snatcher/compare/v1.4.0...v1.5.0) (2020-09-24)
View File
+36
View File
@@ -0,0 +1,36 @@
# Build the source code
FROM node:15.0.1-alpine3.12 AS builder
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /build
COPY package.json package.json
COPY package-lock.json package-lock.json
COPY tsconfig.json tsconfig.json
RUN npm ci
COPY src/ src/
RUN npm run build
RUN npm prune --production
FROM node:15.0.1-alpine3.12
RUN apk add --no-cache chromium
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \
DOCKER=true
RUN addgroup -S appuser && adduser -S -g appuser appuser \
&& mkdir -p /home/appuser/Downloads /app \
&& chown -R appuser:appuser /home/appuser \
&& chown -R appuser:appuser /app
USER appuser
WORKDIR /app
COPY --from=builder /build/node_modules/ node_modules/
COPY --from=builder /build/build/ build/
CMD [ "node", "./build/index.js" ]
+192 -50
View File
@@ -1,4 +1,4 @@
# nvidia-snatcher [![ci](https://github.com/jef/nvidia-snatcher/workflows/ci/badge.svg)](https://github.com/jef/nvidia-snatcher/actions?query=workflow%3Aci) [![discord](https://img.shields.io/discord/756303724095471617.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/Cyc7nrz)
# nvidia-snatcher [![ci](https://github.com/jef/nvidia-snatcher/workflows/ci/badge.svg)](https://github.com/jef/nvidia-snatcher/actions?query=workflow%3Aci)
[FAQ](#FAQ) | [Issues](https://github.com/jef/nvidia-snatcher/issues) | [Wiki](https://github.com/jef/nvidia-snatcher/wiki)
@@ -28,13 +28,11 @@ The purpose of this bot is to get an Nvidia card. It tries multiple things to do
</details>
> :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 :)
| | **Adorama** | **Amazon** | **Amazon (CA)** | **ASUS** | **B&H** | **Best Buy** | **Best Buy (CA)** | **EVGA** | **Micro Center** | **Newegg** | **Newegg (CA)** | **Nvidia** | **Office Depot** | **Zotac** |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **3070**| | | | | | | | | | | | | | |
| **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` |
| **3090** | | | | | | `✔` | `✔` | | | `✔` | `✔` | `✔` | | |
| | Adorama | Amazon | Amazon (CA) | ASUS | B&H | Best Buy | Best Buy (CA) | EVGA | Micro Center | Newegg | Newegg (CA) | Nvidia | Office Depot | PNY | Zotac |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **3070**| | | | | | `✔` | `✔` | | | | `✔` | | | `✔` | |
| **3080** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` |
| **3090** | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | `✔` | | `✔` | `✔` | `✔` | | `✔` | `✔` |
## Installation and prerequisites
@@ -44,11 +42,13 @@ You do not need any computer skills, smarts, or anything of that nature. You are
### Quick overview
- [Node.js 14](https://nodejs.org/en/)
#### Native installation
- [Node.js 15](https://nodejs.org/en/)
- [git](https://git-scm.com/)
- Clone this project `git clone https://github.com/jef/nvidia-snatcher.git`
- Run `npm install`
- Copy `.env.example` to a new file `.env` and edit the `.env` file to your liking using your [favorite text editor](https://code.visualstudio.com/)
- Copy `.env-example` to a new file `.env` and edit the `.env` file to your liking using your [favorite text editor](https://code.visualstudio.com/)
- More on this in [customization](#Customization)
- Run `npm run start` to start
@@ -56,51 +56,100 @@ At any point you want the program to stop, use <kbd>Ctrl</kbd> + <kbd>C</kbd>.
> :point_right: Please visit the [wiki](https://github.com/jef/nvidia-snatcher/wiki) if you need more help with installation.
#### Docker image (To run inside containers)
Available via GitHub Container Registry.
| Tag | Note |
|:---:|---|
| `latest` | Latest stable build |
| `nightly` | Latest HEAD build, could be unstable |
Use `docker run --cap-add=SYS_ADMIN -it --rm --env-file ./.env ghcr.io/jef/nvidia-snatcher:nightly` to run.
### Developer notes
The command `npm run start:dev` can be used instead of `npm run start` to automatically restart the project when filesystem changes are detected in the `src/` folder or `.env` file.
### Customization
To customize `nvidia-snatcher`, make a copy of `.env-example` as `.env` and make any changes to your liking. _All environment variables are **optional**._
Here is a list of variables that you can use to customize your newly copied `.env` file:
| **Environment variable** | **Description** | **Notes** |
| Environment variable | Description | Notes |
|:---:|---|---|
| `BROWSER_TRUSTED` | Skip Chromium Sandbox | Useful for containerized environments, default: `false` |
| `COUNTRY` | [Supported country](#supported-countries) you want to be scraped | Currently only used by Nvidia, default: `usa` |
| `DESKTOP_NOTIFICATIONS` | Display desktop notifications using [node-notifier](https://www.npmjs.com/package/node-notifier) | Default: `false` |
| `DISCORD_NOTIFY_GROUP` | Discord group you would like to notify | Can be comma separated, use role ID, E.g.: `<@2834729847239842>` |
| `DISCORD_WEB_HOOK` | Discord Web Hook URL | Can be comma separated, use whole webhook URL |
| `EMAIL_USERNAME` | Gmail address | E.g.: `jensen.robbed.us@gmail.com` |
| `EMAIL_PASSWORD` | Gmail password | See below if you have MFA |
| `EMAIL_TO` | Destination Email | Defaults to username if not set. Can be comma separated |
| `EMAIL_USERNAME` | Gmail address | E.g.: `jensen.robbed.us@gmail.com` |
| `HEADLESS` | Puppeteer to run headless or not | Debugging related, default: `true` |
| `IN_STOCK_WAIT_TIME` | Time to wait between requests to the same store if it has cards in stock | In seconds, default: `0` |
| `INCOGNITO` | Puppeteer to run incognito or not | Debugging related, default: `false` |
| `IN_STOCK_WAIT_TIME` | Time to wait between requests to the same link if it has that card in stock | In seconds, default: `0` |
| `LOG_LEVEL` | [Logging levels](https://github.com/winstonjs/winston#logging-levels) | Debugging related, default: `info` |
| `MICROCENTER_LOCATION` | Specific MicroCenter location to search | Default : `web` |
| `LOW_BANDWIDTH` | Blocks images/fonts to reduce traffic | Disables ad blocker, default: `false` |
| `MAX_PRICE_SERIES_3070` | Maximum price allowed for a match, applies 3070 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
| `MAX_PRICE_SERIES_3080` | Maximum price allowed for a match, applies 3080 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
| `MAX_PRICE_SERIES_3090` | Maximum price allowed for a match, applies 3090 series cards (does not apply to these sites: Nvidia, Asus, EVGA) | Default: leave empty for no limit, otherwise enter a price (enter whole dollar amounts only, avoid use of: dollar symbols, commas, and periods.) e.g.: `1234` - Cards above `1234` will be skipped. |
| `MICROCENTER_LOCATION` | Specific MicroCenter location(s) to search | Comma separated, e.g.: `marietta,duluth`, default: `web` |
| `MQTT_BROKER_ADDRESS` | IP address or URL of MQTT Broker | e.g 192.168.1.xxx or broker.hivemq.com
| `MQTT_BROKER_PORT` | Network port of MQTT Broker | Default: 1883
| `MQTT_CLIENT_ID` | Unique ClientID (only if required by MQTT Broker), typically not required when only publishing alerts | eg. client-123456
| `MQTT_PASSWORD` | MQTT password - only use with MQTT brokers on private networks, if required. Will not be sent over public networks for safety. | e.g mysecret
| `MQTT_QOS` | QoS level for published alerts to broker (https://www.npmjs.com/package/mqtt#about-qos) | Default: 0, Can be 0, 1, or 2
| `MQTT_TOPIC` | Topic to publish alerts to. Can include %store%, %series%, %brand%, %model% for dynamic topics | Default: nvidia-snatcher/alert e.g nv-alert/%store%/%series%/%brand%/%model%/alert
| `MQTT_USERNAME` | MQTT username - (only if required by MQTT Broker) | e.g myusername
| `NVIDIA_ADD_TO_CART_ATTEMPTS` | The maximum number of times the `nvidia-api` add to cart feature will be attempted before failing | Default: `10` |
| `NVIDIA_SESSION_TTL` | The time in milliseconds to keep the cart active while using `nvidia-api` | Default: `60000` |
| `OPEN_BROWSER` | Toggle for whether or not the browser should open when item is found | Default: `true` |
| `PAGERDUTY_INTEGRATION_KEY` | PagerDuty Events API v2 Integration Key. Obtain one in PagerDuty - <Service you want to use> - Integrations | |
| `PAGERDUTY_SEVERITY` | Severity of PagerDuty events | Default: `info` |
| `PAGE_BACKOFF_MIN` | Minimum backoff time between retrying requests for the same store when a forbidden response is received | Default: `10000` |
| `PAGE_BACKOFF_MAX` | Maximum backoff time between retrying requests for the same store when a forbidden response is received | Default: `3600000` |
| `PAGE_SLEEP_MIN` | Minimum sleep time between queries of the same product page | In milliseconds, default: `5000` |
| `PAGE_SLEEP_MAX` | Maximum sleep time between queries of the same product page | In milliseconds, default: `10000` |
| `PAGE_TIMEOUT` | Navigation Timeout in milliseconds | `0` for infinite, default: `30000` |
| `PHONE_NUMBER` | 10 digit phone number | E.g.: `1234567890`, email configuration required |
| `PHONE_CARRIER` | [Supported carriers](#supported-carriers) for SMS | Email configuration required |
| `PHONE_NUMBER` | 10 digit phone number | E.g.: `1234567890`, email configuration required |
| `PLAY_SOUND` | Play this sound notification if a card is found | Relative path accepted, valid formats: wav, mp3, flac, E.g.: `path/to/notification.wav`, [free sounds available](https://notificationsounds.com/) |
| `PROXY_ADDRESS` | IP Address or fqdn of proxy server |
| `PROXY_PORT` | TCP Port number on which the proxy is listening for connections | Default: `80` |
| `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` |
| `PUSHOVER_PRIORITY` | Pushover message priority |
| `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_MODELS` | Filter to show specified models | Both supported formats are comma separated <br/><br/>1. Standard E.g.: `founders edition,rog strix` <br/><br/> 2. Advanced E.g: `MODEL:SERIES`, E.g: `founders edition:3090,rog strix` |
| `SHOW_ONLY_SERIES` | Filter to show specified series | Comma separated, e.g.: `3080` |
| `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` |
| `SMTP_ADDRESS` | IP Address or fqdn of smtp server |
| `SMTP_PORT` | TCP Port number on which the smtp server is listening for connections | Default: `25` |
| `STORES` | [Supported stores](#supported-stores) you want to be scraped | Both supported formats are comma separated <br/><br/>1. Standard E.g.: `"nvidia"` <br/><br/> 2. Advanced E.g: `STORE:PAGE_SLEEP_MIN:PAGE_SLEEP_MAX`, E.g: `nvidia:10000:30000` <br/><br/>Default: `nvidia` |
| `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 | |
| `TELEGRAM_CHAT_ID` | Telegram chat ID | Comma seperated, e.g.: `123456789`, `123456789,987654321` |
| `TWILIO_ACCOUNT_SID` | Twilio Account SID | Can be found on twilio.com/console |
| `TWILIO_AUTH_TOKEN` | Twilio Auth Token | Can be found on twilio.com/console |
| `TWILIO_FROM_NUMBER` | Twilio provided phone number to send messages from | Include country code e.g +4401234567890 |
| `TWILIO_TO_NUMBER` | Mobile number to send SMS to | Include country code e.g +4401234567890 |
| `TWITTER_ACCESS_TOKEN_KEY` | Twitter Token Key | |
| `TWITTER_ACCESS_TOKEN_SECRET` | Twitter Token Secret | |
| `TWITTER_CONSUMER_KEY` | Twitter Consumer Key | Generate all Twitter keys at: https://developer.twitter.com/ |
| `TWITTER_CONSUMER_SECRET` | Twitter Consumer Secret | |
| `TWITTER_TWEET_TAGS` | Optional list of hashtags to append to the tweet message | E.g.: `#nvidia #nvidiastock` |
| `TWITCH_CLIENT_ID` | Twitch client ID | |
| `TWITCH_CLIENT_SECRET`| Twitch client secret | |
| `TWITCH_ACCESS_TOKEN` | Twitch access token | |
| `TWITCH_REFRESH_TOKEN` | Twitch refresh token | |
| `TWITCH_CHANNEL` | Twitch channel | |
| `USER_AGENT` | Custom User-Agents headers for HTTP requests | Newline separated, e.g.: `USER_AGENT_STRING1 \n USER_AGENT_STRING2` | | Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` |
| `WEB_PORT` | Starts a webserver to be able to control the bot while it is running; optional | Default: disabled |
> :point_right: 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.
@@ -108,33 +157,117 @@ Here is a list of variables that you can use to customize your newly copied `.en
> :point_right: You can test your notification configuration by running `npm run test:notification`.
> :point_right: Data usage is [known to be high](https://github.com/jef/nvidia-snatcher/issues?q=is%3Aissue+sort%3Aupdated-desc+bandwidth). This is expected as the program scrapes many websites in parallel 24/7. To help reduce this, use `LOW_BANDWIDTH="true"`. We are looking into other solutions as well, but is low priority.
#### Supported stores
| **Stores** | **Environment variable** |
| Stores | Environment variable |
|:---:|:---:|
| Adorama | `adorama`|
| Alternate (DE) | `alternate`|
| Alternate (NL) | `alternate-nl`|
| Amazon | `amazon`|
| Amazon (CA) | `amazon-ca`|
| Amazon (DE) | `amazon-de`|
| Amazon (ES) | `amazon-es`|
| Amazon (NL) | `amazon-nl`|
| Amazon (UK) | `amazon-uk`|
| Aria PC (UK) | `aria`|
| ASUS | `asus` |
| ASUS (DE) | `asus-de` |
| Azerty (NL) | `azerty`|
| B&H | `bandh`|
| Best Buy | `bestbuy`|
| Best Buy (CA) | `bestbuy-ca`|
| Box (UK) | `box`|
| Caseking (DE) | `caseking`|
| CCL (UK) | `ccl`|
| Computeruniverse (DE) | `computeruniverse` |
| Coolblue (NL) | `coolblue`|
| Coolmod (ES) | `coolmod`|
| Currys (UK) | `currys`|
| Cyberport (DE) | `cyberport` |
| eBuyer (UK) | `ebuyer`|
| EVGA | `evga`|
| EVGA (EU) | `evga-eu`|
| Gamestop | `gamestop`|
| Mediamarkt (DE) | `mediamarkt`|
| Micro Center | `microcenter`|
| Mindfactory (DE) | `mindfactory` |
| Newegg | `newegg`|
| Newegg (CA) | `newegg-ca`|
| Notebooksbilliger (DE) |`notebooksbilliger`|
| Novatech (UK) | `novatech`|
| Nvidia | `nvidia`|
| Nvidia (API) | `nvidia-api`|
| Office Depot | `officedepot`|
| Overclockers (UK) | `overclockers`|
| PCComponentes (ES) | `pccomponentes`|
| PNY | `pny`|
| Proshop (DE) | `proshop-de`|
| Proshop (DK) | `proshop-dk`|
| Saturn (DE) | `saturn`|
| Scan (UK) | `scan`|
| Very (UK) | `very`|
| Zotac | `zotac`|
<details>
<summary>Micro Center stores</summary>
> :point_right: Before using `web`, please review [this issue comment](https://github.com/jef/nvidia-snatcher/issues/442#issuecomment-703297393).
| Store name |
|:---:|
| `brooklyn` |
| `brentwood` |
| `cambridge` |
| `chicago` |
| `columbus` |
| `dallas` |
| `denver` |
| `duluth` |
| `fairfax` |
| `flushing` |
| `houston` |
| `madison-heights` |
| `marietta` |
| `mayfield-heights` |
| `north-jersey` |
| `overland-park` |
| `parkville` |
| `rockville` |
| `sharonville` |
| `st-davids` |
| `st-louis-park` |
| `tustin` |
| `westbury` |
| `westmont` |
| `yonkers` |
</details>
#### Supported Brands and Models
| Brand | Model |
|:---:|---|
| `asus` | `dual`, `dual oc`, `rog strix`, `rog strix oc`, `tuf`, `tuf oc` |
| `evga` | `ftw3`, `ftw3 ultra`, `xc3`, `xc3 black`, `xc3 ultra` |
| `gainward` | `phantom gs`, `phoenix`, `phoenix gs`, `phoenix gs oc` |
| `gigabyte` | `aorus master`, `aorus xtreme`, `eagle`, `eagle oc`, `gaming`, `gaming oc`, `turbo`, `vision`, `vision oc` |
| `inno3d` | `gaming x3`, `ichill x3`, `ichill x4`, `twin x2 oc` |
| `kfa2` | `sg oc` |
| `msi` | `gaming x trio`, `ventus 2x oc`, `ventus 3x`, `ventus 3x oc` |
| `nvidia` | `founders edition` |
| `palit` | `gamerock oc`, `gaming pro`, `gaming pro oc` |
| `pny` | `dual fan`, `xlr8`, `xlr8 rgb` |
| `zotac` | `amp holo`, `amp extreme holo`, `trinity`, `trinity oc`, `twin edge`, `twin edge oc` |
#### Supported carriers
| **Carrier** | **Environment variable** | **Notes** |
| Carrier | Environment variable | Notes |
|:---:|:---:|:---:|
| AT&T | `att`| |
| AT&T | `att` | |
| AT&T Prepaid | `attgo` | |
| Bell | `bell` | |
| Fido | `fido` | |
| Google | `google`| |
@@ -150,27 +283,29 @@ Here is a list of variables that you can use to customize your newly copied `.en
#### 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 |
`COUNTRY` is only used by the `nvidia` and `nvidia-api` stores.
| Country | 3080 FE | 3090 FE | Test Card | Notes |
|:---:|:---:|:---:|:---:|:---:|
| austria | `✔` | `✔` | `✔` | |
| belgium | `✔` | `✔` | `✔` | |
| canada | `✔` | `✔` | `✔` | |
| czechia | `✔` | `✔` | `✔` | |
| denmark | `✔` | | `✔` | Missing RTX 3090 |
| finland | `✔` | | `✔` | Missing RTX 3090 |
| france | `✔` | `✔` | `✔` | |
| germany | `✔` | `✔` | `✔` | |
| great_britain | `✔` | `✔` | `✔` | |
| ireland | `✔` | `✔` | `✔` | |
| italy | `✔` | `✔` | `✔` | |
| luxembourg | `✔` | `✔` | `✔` | |
| netherlands | `✔` | `✔` | `✔` | |
| norway | `✔` | `✔` | `✔` | |
| poland | `✔` | `✔` | `✔` | |
| portugal | `✔` | | | RTX 3080 only |
| spain | `✔` | `✔` | `✔` | |
| sweden | `✔` | `✔` | `✔` | |
| usa | `✔` | `✔` | `✔` | |
## FAQ
@@ -178,11 +313,18 @@ Here is a list of variables that you can use to customize your newly copied `.en
**Q: Will this harm my computer?** No.
**Q: Have you gotten a card yet?** No. :cry:
**Q: Have you gotten a card yet?** YES! :tada: :rocket:
<details>
<summary>Screenshot</summary>
![screenshot](https://i.imgur.com/59CRzGq.png)
</details>
**Q: Will I get banned from of the stores?** Perhaps, but getting a card is a nice outcome.
**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!** Join the [Discord](https://discord.gg/Cyc7nrz) or 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)
+8
View File
@@ -0,0 +1,8 @@
{
"exec": "ts-node --files ./src/index",
"ext": "ts",
"watch": [
"src/",
".env"
]
}
+5958 -1041
View File
File diff suppressed because it is too large Load Diff
+27 -15
View File
@@ -1,13 +1,14 @@
{
"name": "nvidia-snatcher",
"version": "1.5.0",
"description": "🔮 For all your Nvidia needs",
"main": "src/index.ts",
"scripts": {
"build": "rimraf ./build && tsc",
"all": "npm run build && npm run lint",
"build": "tsc",
"lint": "xo",
"lint:fix": "xo --fix",
"start": "npm run build && node build/index.js",
"start:dev": "nodemon --config nodemon.json",
"test:notification": "npm run build && node build/__test__/notification-test.js"
},
"repository": {
@@ -22,34 +23,45 @@
},
"homepage": "https://github.com/jef/nvidia-snatcher#readme",
"dependencies": {
"@jef/pushbullet": "^2.4.3",
"@slack/web-api": "^5.13.0",
"chalk": "^4.1.0",
"cheerio": "^1.0.0-rc.3",
"discord-webhook-node": "^1.1.8",
"dotenv": "^8.2.0",
"messaging-api-telegram": "^1.0.1",
"mqtt": "^4.2.3",
"node-notifier": "^8.0.0",
"nodemailer": "^6.4.11",
"open": "^7.2.1",
"puppeteer": "^5.3.1",
"node-pagerduty": "^1.3.4",
"nodemailer": "^6.4.14",
"open": "^7.3.0",
"play-sound": "^1.1.3",
"puppeteer": "^5.4.1",
"puppeteer-extra": "^3.1.15",
"puppeteer-extra-plugin-adblocker": "^2.11.6",
"puppeteer-extra-plugin-stealth": "^2.6.1",
"pushbullet": "^2.4.0",
"puppeteer-extra-plugin-adblocker": "^2.11.7",
"puppeteer-extra-plugin-block-resources": "^2.2.7",
"puppeteer-extra-plugin-stealth": "^2.6.3",
"pushover-notifications": "^1.2.2",
"twilio": "^3.50.0",
"twitch": "^4.2.7",
"twitch-auth": "^4.2.7",
"twitch-chat-client": "^4.2.7",
"twitter": "^1.7.1",
"winston": "^3.3.3"
},
"devDependencies": {
"@slack/web-api": "^5.12.0",
"@types/async": "^3.2.3",
"@types/node": "^14.11.2",
"@types/cheerio": "^0.22.22",
"@types/node": "^14.14.6",
"@types/node-notifier": "^8.0.0",
"@types/nodemailer": "^6.4.0",
"@types/puppeteer": "^3.0.2",
"@types/twitter": "^1.7.0",
"discord-webhook-node": "^1.1.8",
"husky": "^4.3.0",
"play-sound": "^1.1.3",
"rimraf": "^3.0.2",
"typescript": "^4.0.2",
"nodemon": "^2.0.6",
"ts-node": "^9.0.0",
"typescript": "^4.0.5",
"webpack": "^5.3.2",
"xo": "^0.33.1"
},
"xo": {
@@ -61,7 +73,7 @@
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
"pre-commit": "npm run all"
}
}
}
+3 -1
View File
@@ -7,5 +7,7 @@ export const adBlocker = new PuppeteerExtraPluginAdblocker({
export async function disableBlockerInPage(page: Page) {
const blockerObject = await adBlocker.getBlocker();
await blockerObject.disableBlockingInPage(page);
if (blockerObject.isBlockingEnabled(page)) {
await blockerObject.disableBlockingInPage(page);
}
}
+170 -20
View File
@@ -1,10 +1,11 @@
import {banner} from './banner';
console.log(banner);
import {config} from 'dotenv';
console.info(banner);
import {config as config_} from 'dotenv';
import path from 'path';
config({path: path.resolve(__dirname, '../.env')});
config_({path: path.resolve(__dirname, '../.env')});
/**
* Returns environment variable, given array, or default array.
@@ -13,7 +14,9 @@ config({path: path.resolve(__dirname, '../.env')});
* @param array Default array. If not set, is `[]`.
*/
function envOrArray(environment: string | undefined, array?: string[]): string[] {
return environment ? environment.split(',') : (array ?? []);
return (environment ? (
environment.includes('\n') ? environment.split('\n') : environment.split(',')
) : (array ?? [])).map(s => s.trim());
}
/**
@@ -43,18 +46,84 @@ function envOrString(environment: string | undefined, string?: string): string {
* @param number Default number. If not set, is `0`.
*/
function envOrNumber(environment: string | undefined, number?: number): number {
return Number(environment ?? (number ?? 0));
return environment ? Number(environment) : (number ?? 0);
}
/**
* Returns environment variable, given number, or default number,
* while handling .env input errors for a Min/Max pair.
* .env errors handled:
* - Min/Max swapped (Min larger than Max, Max smaller than Min)
* - Min larger than default Max when no Max defined
* - Max smaller than default Min when no Min defined
*
* @param environmentMin Min environment variable of Min/Max pair.
* @param environmentMax Max environment variable of Min/Max pair.
* @param number Default number. If not set, is `0`.
*/
function envOrNumberMin(environmentMin: string | undefined, environmentMax: string | undefined, number?: number) {
if (environmentMin || environmentMax) {
if (environmentMin && environmentMax) {
return Number(Number(environmentMin) < Number(environmentMax) ? environmentMin : environmentMax);
}
if (environmentMax) {
return Number(environmentMax) < (number ?? 0) ? Number(environmentMax) : (number ?? 0);
}
if (environmentMin) {
return Number(environmentMin);
}
}
return number ?? 0;
}
/**
* Returns environment variable, given number, or default number,
* while handling .env input errors for a Min/Max pair.
* .env errors handled:
* - Min/Max swapped (Min larger than Max, Max smaller than Min)
* - Min larger than default Max when no Max defined
* - Max smaller than default Min when no Min defined
*
* @param environmentMin Min environment variable of Min/Max pair.
* @param environmentMax Max environment variable of Min/Max pair.
* @param number Default number. If not set, is `0`.
*/
function envOrNumberMax(environmentMin: string | undefined, environmentMax: string | undefined, number?: number) {
if (environmentMin || environmentMax) {
if (environmentMin && environmentMax) {
return Number(Number(environmentMin) < Number(environmentMax) ? environmentMax : environmentMax);
}
if (environmentMin) {
return Number(environmentMin) > (number ?? 0) ? Number(environmentMin) : (number ?? 0);
}
if (environmentMax) {
return Number(environmentMax);
}
}
return number ?? 0;
}
const browser = {
isHeadless: envOrBoolean(process.env.HEADLESS),
isIncognito: envOrBoolean(process.env.INCOGNITO, false),
isTrusted: envOrBoolean(process.env.BROWSER_TRUSTED, false),
maxSleep: envOrNumber(process.env.PAGE_SLEEP_MAX, 10000),
minSleep: envOrNumber(process.env.PAGE_SLEEP_MIN, 5000),
lowBandwidth: envOrBoolean(process.env.LOW_BANDWIDTH, false),
maxBackoff: envOrNumberMax(process.env.PAGE_BACKOFF_MIN, process.env.PAGE_BACKOFF_MAX, 3600000),
maxSleep: envOrNumberMax(process.env.PAGE_SLEEP_MIN, process.env.PAGE_SLEEP_MAX, 10000),
minBackoff: envOrNumberMin(process.env.PAGE_BACKOFF_MIN, process.env.PAGE_BACKOFF_MAX, 10000),
minSleep: envOrNumberMin(process.env.PAGE_SLEEP_MIN, process.env.PAGE_SLEEP_MAX, 5000),
open: envOrBoolean(process.env.OPEN_BROWSER)
};
const logLevel = process.env.LOG_LEVEL ?? 'info';
const docker = envOrBoolean(process.env.DOCKER);
const logLevel = envOrString(process.env.LOG_LEVEL, 'info');
const notifications = {
desktop: process.env.DESKTOP_NOTIFICATIONS === 'true',
@@ -64,11 +133,28 @@ const notifications = {
},
email: {
password: envOrString(process.env.EMAIL_PASSWORD),
smtpAddress: envOrString(process.env.SMTP_ADDRESS),
smtpPort: envOrNumber(process.env.SMTP_PORT, 25),
to: envOrString(process.env.EMAIL_TO, envOrString(process.env.EMAIL_USERNAME)),
username: envOrString(process.env.EMAIL_USERNAME)
},
mqtt: {
broker: envOrString(process.env.MQTT_BROKER_ADDRESS),
clientId: envOrString(process.env.MQTT_CLIENT_ID),
password: envOrString(process.env.MQTT_PASSWORD),
port: envOrNumber(process.env.MQTT_BROKER_PORT, 1883),
qos: envOrNumber(process.env.MQTT_QOS, 0),
topic: envOrString(process.env.MQTT_TOPIC, 'nvidia-snatcher/alert'),
username: envOrString(process.env.MQTT_USERNAME)
},
pagerduty: {
integrationKey: envOrString(process.env.PAGERDUTY_INTEGRATION_KEY),
severity: envOrString(process.env.PAGERDUTY_SEVERITY, 'info')
},
phone: {
availableCarriers: new Map([
['att', 'txt.att.net'],
['attgo', 'mms.att.net'],
['bell', 'txt.bell.ca'],
['fido', 'fido.ca'],
['google', 'msg.fi.google.com'],
@@ -86,8 +172,9 @@ const notifications = {
number: envOrString(process.env.PHONE_NUMBER)
},
playSound: envOrString(process.env.PLAY_SOUND),
pushBulletApiKey: envOrString(process.env.PUSHBULLET),
pushbullet: envOrString(process.env.PUSHBULLET),
pushover: {
priority: envOrNumber(process.env.PUSHOVER_PRIORITY),
token: envOrString(process.env.PUSHOVER_TOKEN),
username: envOrString(process.env.PUSHOVER_USER)
},
@@ -97,7 +184,20 @@ const notifications = {
},
telegram: {
accessToken: envOrString(process.env.TELEGRAM_ACCESS_TOKEN),
chatId: envOrString(process.env.TELEGRAM_CHAT_ID)
chatId: envOrArray(process.env.TELEGRAM_CHAT_ID)
},
twilio: {
accountSid: envOrString(process.env.TWILIO_ACCOUNT_SID),
authToken: envOrString(process.env.TWILIO_AUTH_TOKEN),
from: envOrString(process.env.TWILIO_FROM_NUMBER),
to: envOrString(process.env.TWILIO_TO_NUMBER)
},
twitch: {
accessToken: envOrString(process.env.TWITCH_ACCESS_TOKEN),
channel: envOrString(process.env.TWITCH_CHANNEL),
clientId: envOrString(process.env.TWITCH_CLIENT_ID),
clientSecret: envOrString(process.env.TWITCH_CLIENT_SECRET),
refreshToken: envOrString(process.env.TWITCH_REFRESH_TOKEN)
},
twitter: {
accessTokenKey: envOrString(process.env.TWITTER_ACCESS_TOKEN_KEY),
@@ -108,28 +208,78 @@ const notifications = {
}
};
const nvidia = {
addToCardAttempts: envOrNumber(process.env.NVIDIA_ADD_TO_CART_ATTEMPTS, 10),
sessionTtl: envOrNumber(process.env.NVIDIA_SESSION_TTL, 60000)
};
const page = {
height: 1080,
inStockWaitTime: envOrNumber(process.env.IN_STOCK_WAIT_TIME),
navigationTimeout: envOrNumber(process.env.PAGE_TIMEOUT, 30000),
screenshot: envOrBoolean(process.env.SCREENSHOT),
userAgent: envOrString(process.env.USER_AGENT, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'),
timeout: envOrNumber(process.env.PAGE_TIMEOUT, 30000),
userAgents: envOrArray(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 store = {
country: envOrString(process.env.COUNTRY, 'usa'),
microCenterLocation: envOrString(process.env.MICROCENTER_LOCATION, 'web'),
showOnlyBrands: envOrArray(process.env.SHOW_ONLY_BRANDS),
showOnlyModels: envOrArray(process.env.SHOW_ONLY_MODELS),
showOnlySeries: envOrArray(process.env.SHOW_ONLY_SERIES, ['3070', '3080', '3090']),
stores: envOrArray(process.env.STORES, ['nvidia'])
const proxy = {
address: envOrString(process.env.PROXY_ADDRESS),
port: envOrNumber(process.env.PROXY_PORT, 80)
};
export const Config = {
// Check for deprecated configuration values
if (process.env.MAX_PRICE) {
console.warn(' MAX_PRICE is deprecated, please use MAX_PRICE_SERIES_{{series}}');
}
const store = {
country: envOrString(process.env.COUNTRY, 'usa'),
maxPrice: {
series: {
3070: envOrNumber(process.env.MAX_PRICE_SERIES_3070),
3080: envOrNumber(process.env.MAX_PRICE_SERIES_3080),
3090: envOrNumber(process.env.MAX_PRICE_SERIES_3090)
}
},
microCenterLocation: envOrArray(process.env.MICROCENTER_LOCATION, ['web']),
showOnlyBrands: envOrArray(process.env.SHOW_ONLY_BRANDS),
showOnlyModels: envOrArray(process.env.SHOW_ONLY_MODELS).map(entry => {
const [name, series] = entry.match(/[^:]+/g) ?? [];
return {
name: envOrString(name),
series: envOrString(series)
};
}),
showOnlySeries: envOrArray(process.env.SHOW_ONLY_SERIES, ['3070', '3080', '3090']),
stores: envOrArray(process.env.STORES, ['nvidia']).map(entry => {
const [name, minPageSleep, maxPageSleep] = entry.match(/[^:]+/g) ?? [];
return {
maxPageSleep: envOrNumberMax(minPageSleep, maxPageSleep, browser.maxSleep),
minPageSleep: envOrNumberMin(minPageSleep, maxPageSleep, browser.minSleep),
name: envOrString(name)
};
})
};
export const defaultStoreData = {
maxPageSleep: browser.maxSleep,
minPageSleep: browser.minSleep
};
export const config = {
browser,
docker,
logLevel,
notifications,
nvidia,
page,
proxy,
store
};
export function setConfig(newConfig: any) {
const writeConfig = config as any;
for (const key of Object.keys(newConfig)) {
writeConfig[key] = newConfig[key];
}
}
+67 -22
View File
@@ -1,58 +1,103 @@
import {Config} from './config';
import {Logger} from './logger';
import {Stores} from './store/model';
import {startAPIServer, stopAPIServer} from './web';
import {Browser} from 'puppeteer';
import {adBlocker} from './adblocker';
import {config} from './config';
import {getSleepTime} from './util';
import {logger} from './logger';
import puppeteer from 'puppeteer-extra';
import resourceBlock from 'puppeteer-extra-plugin-block-resources';
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
import {storeList} from './store/model';
import {tryLookupAndLoop} from './store';
puppeteer.use(stealthPlugin());
puppeteer.use(adBlocker);
if (config.browser.lowBandwidth) {
puppeteer.use(resourceBlock({
blockedTypes: new Set(['image', 'font'] as const)
}));
} else {
puppeteer.use(adBlocker);
}
let browser: Browser | undefined;
/**
* Starts the bot.
*/
async function main() {
if (Stores.length === 0) {
Logger.error('✖ no stores selected', Stores);
return;
}
const args: string[] = [];
// Skip Chromium Linux Sandbox
// https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#setting-up-chrome-linux-sandbox
if (Config.browser.isTrusted) {
if (config.browser.isTrusted) {
args.push('--no-sandbox');
args.push('--disable-setuid-sandbox');
}
const browser = await puppeteer.launch({
// https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#tips
if (config.docker) {
args.push('--disable-dev-shm-usage');
}
// Add the address of the proxy server if defined
if (config.proxy.address) {
args.push(`--proxy-server=http://${config.proxy.address}:${config.proxy.port}`);
}
await stop();
browser = await puppeteer.launch({
args,
defaultViewport: {
height: Config.page.height,
width: Config.page.width
height: config.page.height,
width: config.page.width
},
headless: Config.browser.isHeadless
headless: config.browser.isHeadless
});
for (const store of Stores) {
Logger.debug(store.links);
for (const store of storeList.values()) {
logger.debug('store links', {meta: {links: store.links}});
if (store.setupAction !== undefined) {
store.setupAction(browser);
}
setTimeout(tryLookupAndLoop, getSleepTime(), browser, store);
setTimeout(tryLookupAndLoop, getSleepTime(store), browser, store);
}
await startAPIServer();
}
async function stop() {
await stopAPIServer();
if (browser) {
// Use temporary swap variable to avoid any race condition
const browserTemporary = browser;
browser = undefined;
await browserTemporary.close();
}
}
async function stopAndExit() {
await stop();
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
}
/**
* Will continually run until user interferes.
*/
try {
void main();
} catch (error) {
Logger.error('✖ something bad happened, resetting nvidia-snatcher', error);
void main();
async function loopMain() {
try {
await main();
} catch (error) {
logger.error('✖ something bad happened, resetting nvidia-snatcher in 5 seconds', error);
setTimeout(loopMain, 5000);
}
}
void loopMain();
process.on('SIGINT', stopAndExit);
process.on('SIGQUIT', stopAndExit);
process.on('SIGTERM', stopAndExit);
+80 -13
View File
@@ -1,31 +1,56 @@
import {Link, Store} from './store/model';
import winston, {format} from 'winston';
import {Config} from './config';
import chalk from 'chalk';
import {config} from './config';
import winston from 'winston';
const prettyJson = format.printf(info => {
const prettyJson = winston.format.printf(info => {
const timestamp = new Date().toLocaleTimeString();
if (typeof info.message === 'object') {
info.message = JSON.stringify(info.message, null, 4);
}
if (info.meta) {
return chalk.grey(`[${timestamp}]`) + ` ${info.level} ` + chalk.grey('::') + ` ${info.message} ${chalk.magenta(JSON.stringify(info.meta, null, 2))}`;
}
return chalk.grey(`[${timestamp}]`) + ` ${info.level} ` + chalk.grey('::') + ` ${info.message}`;
});
export const Logger = winston.createLogger({
format: format.combine(
format.colorize(),
format.prettyPrint(),
format.splat(),
format.simple(),
export const logger = winston.createLogger({
format: winston.format.combine(
winston.format.colorize(),
winston.format.prettyPrint(),
winston.format.splat(),
winston.format.simple(),
prettyJson
),
level: Config.logLevel,
level: config.logLevel,
transports: [new winston.transports.Console({})]
});
export const Print = {
backoff(link: Link, store: Store, parameters: {delay: number; statusCode: number}, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow(`BACKOFF DELAY status=${parameters.statusCode} delay=${parameters.delay}`);
}
return `${buildProductString(link, store)} :: BACKOFF DELAY status=${parameters.statusCode} delay=${parameters.delay}`;
},
badStatusCode(link: Link, store: Store, statusCode: number, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow(`STATUS CODE ERROR ${statusCode}`);
}
return `${buildProductString(link, store)} :: STATUS CODE ERROR ${statusCode}`;
},
bannedSeller(link: Link, store: Store, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow('BANNED SELLER');
}
return `${buildProductString(link, store)} :: BANNED SELLER`;
},
captcha(link: Link, store: Store, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow('CAPTCHA');
@@ -33,12 +58,46 @@ export const Print = {
return `${buildProductString(link, store)} :: CAPTCHA`;
},
inStock(link: Link, store: Store, color?: boolean): string {
inStock(link: Link, store: Store, color?: boolean, sms?: boolean): string {
const productString = `${buildProductString(link, store)} :: IN STOCK`;
if (color) {
return chalk.green.bold(`🚀🚨 ${buildProductString(link, store, true)} :: IN STOCK 🚨🚀`);
return chalk.bgGreen.white.bold(`🚀🚨 ${productString} 🚨🚀`);
}
return `🚀🚨 ${buildProductString(link, store)} :: IN STOCK 🚨🚀`;
if (sms) {
return productString;
}
return `🚀🚨 ${productString} 🚨🚀`;
},
inStockWaiting(link: Link, store: Store, color?: boolean): string {
if (color) {
return ' ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow('IN STOCK, WAITING');
}
return ` ${buildProductString(link, store)} :: IN STOCK, WAITING`;
},
maxPrice(link: Link, store: Store, price: number, maxPrice: number, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow(`PRICE ${price} EXCEEDS LIMIT ${maxPrice}`);
}
return `${buildProductString(link, store)} :: PRICE ${price} EXCEEDS LIMIT ${maxPrice}`;
},
message(message: string, topic: string, store: Store, color?: boolean): string {
if (color) {
return '✖ ' + buildSetupString(topic, store, true) + ' :: ' + chalk.yellow(message);
}
return `${buildSetupString(topic, store)} :: ${message}`;
},
noResponse(link: Link, store: Store, color?: boolean): string {
if (color) {
return '✖ ' + buildProductString(link, store, true) + ' :: ' + chalk.yellow('NO RESPONSE');
}
return `${buildProductString(link, store)} :: NO RESPONSE`;
},
outOfStock(link: Link, store: Store, color?: boolean): string {
if (color) {
@@ -56,6 +115,14 @@ export const Print = {
}
};
function buildSetupString(topic: string, store: Store, color?: boolean): string {
if (color) {
return chalk.cyan(`[${store.name}]`) + chalk.grey(` [setup (${topic})]`);
}
return `[${store.name}] [setup (${topic})]`;
}
function buildProductString(link: Link, store: Store, color?: boolean): string {
if (color) {
return chalk.cyan(`[${store.name}]`) + chalk.grey(` [${link.brand} (${link.series})] ${link.model}`);
+15 -9
View File
@@ -1,14 +1,20 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Print, logger} from '../logger';
import {config} from '../config';
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)
});
const desktop = config.notifications.desktop;
Logger.info('✔ desktop notification sent');
})();
export function sendDesktopNotification(link: Link, store: Store) {
if (desktop) {
logger.debug('↗ sending desktop notification');
(async () => {
notifier.notify({
message: link.cartUrl ? link.cartUrl : link.url,
title: Print.inStock(link, store)
});
logger.info('✔ desktop notification sent');
})();
}
}
+36 -30
View File
@@ -1,38 +1,44 @@
import {Link, Store} from '../store/model';
import {MessageBuilder, Webhook} from 'discord-webhook-node';
import {Config} from '../config';
import {Logger} from '../logger';
import {config} from '../config';
import {logger} from '../logger';
const hooks = Config.notifications.discord.webHookUrl;
const notifyGroup = Config.notifications.discord.notifyGroup;
const discord = config.notifications.discord;
const hooks = discord.webHookUrl;
const notifyGroup = 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 (discord.webHookUrl.length > 0) {
logger.debug('↗ sending discord message');
if (notifyGroup) {
embed.setText(notifyGroup.join(' '));
(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('Series', link.series, 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);
}
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);
}
})();
})();
}
}
+43 -28
View File
@@ -1,38 +1,53 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Mail from 'nodemailer/lib/mailer';
import {config} from '../config';
import nodemailer from 'nodemailer';
const email = Config.notifications.email;
const email = config.notifications.email;
const transporter = nodemailer.createTransport({
auth: {
pass: email.password,
user: email.username
},
service: 'gmail'
const transportOptions: any = {};
if (email.username && (email.password || email.smtpAddress)) {
transportOptions.auth = {};
transportOptions.auth.user = email.username;
transportOptions.auth.pass = email.password;
}
if (email.smtpAddress) {
transportOptions.host = email.smtpAddress;
transportOptions.port = email.smtpPort;
} else {
transportOptions.service = 'gmail';
}
export const transporter = nodemailer.createTransport({
...transportOptions
});
export function sendEmail(link: Link, store: Store) {
const mailOptions: Mail.Options = {
attachments: link.screenshot ? [
{
filename: link.screenshot,
path: `./${link.screenshot}`
}
] : undefined,
from: email.username,
subject: Print.inStock(link, store),
text: link.cartUrl ? link.cartUrl : link.url,
to: email.username
};
if (email.username && (email.password || email.smtpAddress)) {
logger.debug('↗ sending email');
transporter.sendMail(mailOptions, error => {
if (error) {
Logger.error('✖ couldn\'t send email', error);
} else {
Logger.info('✔ email sent');
}
});
const mailOptions: Mail.Options = {
attachments: link.screenshot ? [
{
filename: link.screenshot,
path: `./${link.screenshot}`
}
] : undefined,
from: email.username,
subject: Print.inStock(link, store),
text: link.cartUrl ? link.cartUrl : link.url,
to: email.to
};
transporter.sendMail(mailOptions, error => {
if (error) {
logger.error('✖ couldn\'t send email', error);
} else {
logger.info('✔ email sent');
}
});
}
}
+107
View File
@@ -0,0 +1,107 @@
import {Link, Store} from '../store/model';
import MqttClient, {IClientOptions, IClientPublishOptions} from 'mqtt';
import {Print, logger} from '../logger';
import {config} from '../config';
const mqtt = config.notifications.mqtt;
let client: MqttClient.Client;
if (mqtt.broker) {
if (checkInsecureUsage(mqtt.password, mqtt.broker)) {
logger.warn('✖ Insecure transport of password - Only use credentials with MQTT brokers on private networks.');
} else {
const clientOptions: IClientOptions = {
clean: (mqtt.clientId === ''),
clientId: mqtt.clientId === '' ? undefined : mqtt.clientId,
password: mqtt.password === '' ? undefined : mqtt.password,
username: mqtt.username === '' ? undefined : mqtt.username
};
client = MqttClient.connect(`mqtt://${mqtt.broker}:${mqtt.port}`, clientOptions);
}
}
export function sendMqttMessage(link: Link, store: Store) {
if (client) {
logger.debug('↗ sending mqtt message');
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
const message = `{"msg":"${Print.inStock(link, store)}", "url":"${givenUrl}"}`;
const topic = generateTopic(link, store, mqtt.topic);
const pubOptions: IClientPublishOptions = {
qos: mqtt.qos as 0 | 1 | 2,
retain: false
};
try {
client.publish(
topic,
message,
pubOptions
);
logger.info('✔ mqtt message sent');
} catch (error) {
logger.error('✖ couldn\'t send mqtt message', error);
}
})();
}
}
function generateTopic(link: Link, store: Store, topic: string): string {
topic.trim();
topic = topic.replace(/^\//, '');
topic = topic.replace(/%series%/g, link.series)
.replace(/%brand%/g, link.brand)
.replace(/%model%/g, link.model)
.replace(/%store%/g, store.name);
return topic;
}
/**
* Basic protection against sending credentials in the clear over public networks.
* - Returns 'true' if password is supplied in .env but address/URL is not part of a private network
* - Private networks evaluated: Class A, B, or C private IP's or linklocal URL ("*.local")
* - TLS could be implemented, however, the majority of MQTT services on the internet do not require user authentication.
* - If you find a 'cloud' MQTT broker requiring authentication for publishing alerts, consider using another MQTT service (for now).
*
*/
function checkInsecureUsage(pass: string, address: string): boolean {
if (pass !== '') {
if (isClassANet(address) ||
isClassBNet(address) ||
isClassCNet(address) ||
isLinkLocal(address)) {
logger.debug(`MQTT using private network broker: ${address}`);
} else {
logger.debug(`MQTT using public network broker: ${address}`);
return true;
}
}
return false;
}
function isClassANet(address: string): boolean {
const classRegex = /^(10\.(\d|[1-9]\d|[12][0-5]{2})\.(\d|[1-9]\d|[12][0-5]{2})\.(\d|[1-9]\d|[12][0-5]{2}))$/;
return Boolean(classRegex.exec(address));
}
function isClassBNet(address: string): boolean {
const classRegex = /^(172\.(1[6-9]|2\d|3[01])\.(\d|[1-9]\d|[12][0-5]{2})\.(\d|[1-9]\d|[12][0-5]{2}))$/;
return Boolean(classRegex.exec(address));
}
function isClassCNet(address: string): boolean {
const classRegex = /^(192\.168\.(\d|[1-9]\d|[12][0-5]{2})\.(\d|[1-9]\d|[12][0-5]{2}))$/;
return Boolean(classRegex.exec(address));
}
function isLinkLocal(address: string): boolean {
const linkLocal = /.+\.local$/;
return Boolean(linkLocal.exec(address));
}
+22 -63
View File
@@ -1,75 +1,34 @@
import {Link, Store} from '../store/model';
import {Config} from '../config';
import {Logger} from '../logger';
import {playSound} from './sound';
import {sendDesktopNotification} from './desktop';
import {sendDiscordMessage} from './discord';
import {sendEmail} from './email';
import {sendPushBulletNotification} from './pushbullet';
import {sendMqttMessage} from './mqtt';
import {sendPagerDutyNotification} from './pagerduty';
import {sendPushbulletNotification} from './pushbullet';
import {sendPushoverNotification} from './pushover';
import {sendSMS} from './sms';
import {sendSlackMessage} from './slack';
import {sendSms} from './sms';
import {sendTelegramMessage} from './telegram';
import {sendTweet} from './twitter';
const notifications = Config.notifications;
import {sendTwilioMessage} from './twilio';
import {sendTwitchMessage} from './twitch';
export function sendNotification(link: Link, store: Store) {
if (notifications.email.username && notifications.email.password) {
Logger.debug('↗ sending email');
sendEmail(link, store);
}
if (notifications.phone.number) {
Logger.debug('↗ sending sms');
const carrier = notifications.phone.carrier;
if (carrier && notifications.phone.availableCarriers.has(carrier)) {
sendSMS(link, store);
}
}
if (notifications.playSound) {
Logger.debug('↗ playing sound');
playSound();
}
if (notifications.desktop) {
Logger.debug('↗ sending desktop notification');
sendDesktopNotification(link, store);
}
if (notifications.discord.webHookUrl) {
Logger.debug('↗ sending discord message');
sendDiscordMessage(link, store);
}
if (notifications.slack.channel && notifications.slack.token) {
Logger.debug('↗ sending slack message');
sendSlackMessage(link, store);
}
if (notifications.telegram.accessToken && notifications.telegram.chatId) {
Logger.debug('↗ sending telegram message');
sendTelegramMessage(link, store);
}
if (notifications.pushBulletApiKey) {
Logger.debug('↗ sending pushbullet message');
sendPushBulletNotification(link, store);
}
if (notifications.pushover.token && notifications.pushover.username) {
Logger.debug('↗ sending pushover message');
sendPushoverNotification(link, store);
}
if (
notifications.twitter.accessTokenKey &&
notifications.twitter.accessTokenSecret &&
notifications.twitter.consumerKey &&
notifications.twitter.consumerSecret
) {
Logger.debug('↗ sending twitter message');
sendTweet(link, store);
}
// Priority
playSound();
sendEmail(link, store);
sendSms(link, store);
sendDesktopNotification(link, store);
// Non-priority
sendDiscordMessage(link, store);
sendMqttMessage(link, store);
sendPagerDutyNotification(link, store);
sendPushbulletNotification(link, store);
sendPushoverNotification(link, store);
sendSlackMessage(link, store);
sendTelegramMessage(link, store);
sendTweet(link, store);
sendTwilioMessage(link, store);
sendTwitchMessage(link, store);
}
+32
View File
@@ -0,0 +1,32 @@
import {Link, Store} from '../store/model';
import {Print, logger} from '../logger';
import PDClient from 'node-pagerduty';
import {config} from '../config';
const pd = new PDClient('');
export function sendPagerDutyNotification(link: Link, store: Store) {
if (config.notifications.pagerduty.integrationKey) {
logger.debug('↗ sending pagerduty message');
const links = [
{href: link.url, text: 'Visit Store'}
];
if (link.cartUrl) {
links.push({
href: link.cartUrl, text: 'Add to Cart'
});
}
pd.events.sendEvent({
dedup_key: link.url,
event_action: 'trigger',
payload: {
links,
severity: config.notifications.pagerduty.severity,
source: store.name,
summary: Print.inStock(link, store)
},
routing_key: config.notifications.pagerduty.integrationKey
});
}
}
+21 -17
View File
@@ -1,22 +1,26 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import PushBullet from 'pushbullet';
import {Print, logger} from '../logger';
import PushBullet from '@jef/pushbullet';
import {config} from '../config';
const pushBulletApiKey = Config.notifications.pushBulletApiKey;
const pushbullet = config.notifications.pushbullet;
export function sendPushBulletNotification(link: Link, store: Store) {
const pusher = new PushBullet(pushBulletApiKey);
export function sendPushbulletNotification(link: Link, store: Store) {
if (pushbullet) {
logger.debug('↗ sending pushbullet message');
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');
}
});
const pusher = new PushBullet(pushbullet);
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');
}
});
}
}
+20 -15
View File
@@ -1,25 +1,30 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import Push from 'pushover-notifications';
import {Print, logger} from '../logger';
import Push, {PushoverMessage} from 'pushover-notifications';
import {config} from '../config';
const pushover = Config.notifications.pushover;
const pushover = config.notifications.pushover;
const push = new Push({
token: pushover.token,
user: pushover.username
});
export function sendPushoverNotification(link: Link, store: Store) {
const message = {
message: link.cartUrl ? link.cartUrl : link.url,
title: Print.inStock(link, store)
};
if (pushover.token && pushover.username) {
logger.debug('↗ sending pushover message');
push.send(message, (error: Error) => {
if (error) {
Logger.error('✖ couldn\'t send pushover message', error);
} else {
Logger.info('✔ pushover message sent');
}
});
const message: PushoverMessage = {
message: link.cartUrl ? link.cartUrl : link.url,
priority: pushover.priority,
title: Print.inStock(link, store)
};
push.send(message, (error: Error) => {
if (error) {
logger.error('✖ couldn\'t send pushover message', error);
} else {
logger.info('✔ pushover message sent');
}
});
}
}
+25 -20
View File
@@ -1,30 +1,35 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import {WebClient} from '@slack/web-api';
import {config} from '../config';
const channel = Config.notifications.slack.channel;
const token = Config.notifications.slack.token;
const slack = config.notifications.slack;
const channel = slack.channel;
const token = slack.token;
const web = new WebClient(token);
export function sendSlackMessage(link: Link, store: Store) {
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
if (slack.channel && slack.token) {
logger.debug('↗ sending slack message');
try {
const result = await web.chat.postMessage({
channel,
text: `${Print.inStock(link, store)}\n${givenUrl}`
});
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
if (!result.ok) {
Logger.error('✖ couldn\'t send slack message', result);
return;
try {
const result = await web.chat.postMessage({
channel,
text: `${Print.inStock(link, store)}\n${givenUrl}`
});
if (!result.ok) {
logger.error('✖ couldn\'t send slack message', result);
return;
}
logger.info('✔ slack message sent');
} catch (error) {
logger.error('✖ couldn\'t send slack message', error);
}
Logger.info('✔ slack message sent');
} catch (error) {
Logger.error('✖ couldn\'t send slack message', error);
}
})();
})();
}
}
+32 -33
View File
@@ -1,44 +1,43 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Mail from 'nodemailer/lib/mailer';
import nodemailer from 'nodemailer';
import {config} from '../config';
import {transporter} from './email';
if (Config.notifications.phone.number && !Config.notifications.email.username) {
Logger.warn('✖ in order to recieve sms alerts, email notifications must also be configured');
if (config.notifications.phone.number && !config.notifications.email.username) {
logger.warn('✖ in order to receive sms alerts, email notifications must also be configured');
}
const [email, phone] = [Config.notifications.email, Config.notifications.phone];
const [email, phone] = [config.notifications.email, config.notifications.phone];
const transporter = nodemailer.createTransport({
auth: {
pass: email.password,
user: email.username
},
service: 'gmail'
});
export function sendSms(link: Link, store: Store) {
if (phone.number) {
logger.debug('↗ sending sms');
const carrier = phone.carrier;
export function sendSMS(link: Link, store: Store) {
const mailOptions: Mail.Options = {
attachments: link.screenshot ? [
{
filename: link.screenshot,
path: `./${link.screenshot}`
}
] : undefined,
from: email.username,
subject: Print.inStock(link, store),
text: link.cartUrl ? link.cartUrl : link.url,
to: generateAddress()
};
if (carrier && phone.availableCarriers.has(carrier)) {
const mailOptions: Mail.Options = {
attachments: link.screenshot ? [
{
filename: link.screenshot,
path: `./${link.screenshot}`
}
] : undefined,
from: email.username,
subject: Print.inStock(link, store, false, true),
text: link.cartUrl ? link.cartUrl : link.url,
to: generateAddress()
};
transporter.sendMail(mailOptions, error => {
if (error) {
Logger.error('✖ couldn\'t send sms', error);
} else {
Logger.info('✔ sms sent');
transporter.sendMail(mailOptions, error => {
if (error) {
logger.error('✖ couldn\'t send sms', error);
} else {
logger.info('✔ sms sent');
}
});
}
});
}
}
function generateAddress() {
@@ -48,5 +47,5 @@ function generateAddress() {
return [phone.number, phone.availableCarriers.get(carrier)].join('@');
}
Logger.error('✖ unknown carrier', carrier);
logger.error('✖ unknown carrier', carrier);
}
+16 -14
View File
@@ -1,35 +1,37 @@
import {Config} from '../config';
import {Logger} from '../logger';
import playerLib, {PlaySound} from 'play-sound';
import {config} from '../config';
import fs from 'fs';
import playerLib from 'play-sound';
import {logger} from '../logger';
let player: any;
let player: PlaySound;
if (Config.notifications.playSound) {
if (config.notifications.playSound) {
player = playerLib();
if (player.player === null) {
Logger.warn('✖ couldn\'t find sound player');
logger.warn('✖ couldn\'t find sound player');
} else {
const playerName: string = player.player;
Logger.info(`✔ sound player found: ${playerName}`);
const playerName = player.player;
logger.info(`✔ sound player found: ${playerName}`);
}
}
export function playSound() {
if (player.player !== null) {
fs.access(Config.notifications.playSound, fs.constants.F_OK, error => {
if (config.notifications.playSound && player.player !== null) {
logger.debug('↗ playing sound');
fs.access(config.notifications.playSound, fs.constants.F_OK, error => {
if (error) {
Logger.error(`✖ error opening sound file: ${error.message}`);
logger.error(`✖ error opening sound file: ${error.message}`);
return;
}
player.play(Config.notifications.playSound, (error: Error) => {
player.play(config.notifications.playSound, (error: Error) => {
if (error) {
Logger.error('✖ couldn\'t play sound', error);
logger.error('✖ couldn\'t play sound', error);
}
Logger.info('✔ played sound');
logger.info('✔ played sound');
});
});
}
+21 -12
View File
@@ -1,23 +1,32 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import {TelegramClient} from 'messaging-api-telegram';
import {config} from '../config';
const telegram = Config.notifications.telegram;
const telegram = config.notifications.telegram;
const client = new TelegramClient({
accessToken: telegram.accessToken
});
export function sendTelegramMessage(link: Link, store: Store) {
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
if (telegram.accessToken && telegram.chatId) {
logger.debug('↗ sending telegram message');
try {
await client.sendMessage(telegram.chatId, `${Print.inStock(link, store)}\n${givenUrl}`);
Logger.info('✔ telegram message sent');
} catch (error) {
Logger.error('✖ couldn\'t send telegram message', error);
}
})();
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
const results = [];
for (const chatId of telegram.chatId) {
try {
results.push(client.sendMessage(chatId, `${Print.inStock(link, store)}\n${givenUrl}`));
logger.info('✔ telegram message sent');
} catch (error) {
logger.error('✖ couldn\'t send telegram message', error);
}
}
await Promise.all(results);
})();
}
}
+33
View File
@@ -0,0 +1,33 @@
import {Link, Store} from '../store/model';
import {Print, logger} from '../logger';
import {Twilio} from 'twilio';
import {config} from '../config';
const twilio = config.notifications.twilio;
let client: Twilio;
if (twilio.accountSid && twilio.authToken) {
client = new Twilio(twilio.accountSid, twilio.authToken);
}
export function sendTwilioMessage(link: Link, store: Store) {
if (client) {
logger.debug('↗ sending twilio message');
(async () => {
const givenUrl = link.cartUrl ? link.cartUrl : link.url;
const message = `${Print.inStock(link, store)}\n${givenUrl}`;
try {
await client.messages.create({
body: message,
from: twilio.from,
to: twilio.to
});
logger.info('✔ twilio message sent');
} catch (error) {
logger.error('✖ couldn\'t send twilio message', error);
}
})();
}
}
+78
View File
@@ -0,0 +1,78 @@
import {Link, Store} from '../store/model';
import {Print, logger} from '../logger';
import {RefreshableAuthProvider, StaticAuthProvider} from 'twitch-auth';
import {existsSync, promises, readFileSync} from 'fs';
import {ChatClient} from 'twitch-chat-client';
import {config} from '../config';
const twitch = config.notifications.twitch;
const messages: string[] = [];
let alreadySaying = false;
let tokenData = {
accessToken: twitch.accessToken,
expiryTimestamp: 0,
refreshToken: twitch.refreshToken
};
if (existsSync('./twitch.json')) {
tokenData = {...JSON.parse(readFileSync('./twitch.json', 'utf-8')), ...tokenData};
}
const chatClient: ChatClient = new ChatClient(
new RefreshableAuthProvider(
new StaticAuthProvider(twitch.clientId, tokenData.accessToken),
{
clientSecret: twitch.clientSecret,
expiry: tokenData.expiryTimestamp === null ? null : new Date(tokenData.expiryTimestamp),
onRefresh: async ({accessToken, refreshToken, expiryDate}) => {
return promises.writeFile('./twitch.json', JSON.stringify({
accessToken,
expiryTimestamp: expiryDate === null ? null : expiryDate.getTime(),
refreshToken
}, null, 4), 'utf-8');
},
refreshToken: tokenData.refreshToken
}
),
{
channels: [twitch.channel]
}
);
chatClient.onJoin((channel: string, user: string) => {
if (channel === `#${twitch.channel}` && user === chatClient.currentNick) {
while (messages.length) {
const message: string | undefined = messages.shift();
if (message !== undefined) {
try {
chatClient.say(channel, message);
logger.info('✔ twitch message sent');
} catch (error) {
logger.error('✖ couldn\'t send twitch message', error);
}
}
}
}
void chatClient.quit();
});
chatClient.onDisconnect(() => {
alreadySaying = false;
});
export function sendTwitchMessage(link: Link, store: Store) {
if (tokenData.accessToken && twitch.channel && twitch.clientId && twitch.clientSecret && tokenData.refreshToken) {
logger.debug('↗ sending twitch message');
messages.push(`${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`);
if (!alreadySaying) {
alreadySaying = true;
void chatClient.connect();
}
}
}
+17 -13
View File
@@ -1,9 +1,9 @@
import {Link, Store} from '../store/model';
import {Logger, Print} from '../logger';
import {Config} from '../config';
import {Print, logger} from '../logger';
import Twitter from 'twitter';
import {config} from '../config';
const twitter = Config.notifications.twitter;
const twitter = config.notifications.twitter;
const client = new Twitter({
access_token_key: twitter.accessTokenKey,
@@ -13,17 +13,21 @@ const client = new Twitter({
});
export function sendTweet(link: Link, store: Store) {
let status = `${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`;
if (twitter.accessTokenKey && twitter.accessTokenSecret && twitter.consumerKey && twitter.consumerSecret) {
logger.debug('↗ sending twitter message');
if (twitter.tweetTags) {
status += `\n\n${twitter.tweetTags}`;
}
let status = `${Print.inStock(link, store)}\n${link.cartUrl ? link.cartUrl : link.url}`;
client.post('statuses/update', {status}, error => {
if (error) {
Logger.error('✖ couldn\'t send twitter notification', error);
} else {
Logger.info('✔ twitter notification sent');
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 {Link, Series, Store} from './model';
import {Print, logger} from '../logger';
import {Browser} from 'puppeteer';
import cheerio from 'cheerio';
import {filterSeries} from './filter';
import {usingResponse} from '../util';
function addNewLinks(store: Store, links: Link[], series: Series) {
if (links.length === 0) {
logger.debug(Print.message('NO STORE LINKS FOUND', series, store, true));
return;
}
const existingUrls = new Set(store.links.map(link => link.url));
const newLinks = links.filter(link => !existingUrls.has(link.url));
if (newLinks.length === 0) {
return;
}
logger.debug(Print.message(`FOUND ${newLinks.length} STORE LINKS`, series, store, true));
logger.debug(JSON.stringify(newLinks, null, 2));
store.links = store.links.concat(newLinks);
}
export async function fetchLinks(store: Store, browser: Browser) {
if (!store.linksBuilder) {
return;
}
const promises: Array<Promise<void>> = [];
for (let {series, url} of store.linksBuilder.urls) {
if (!filterSeries(series)) {
continue;
}
logger.debug(Print.message('DETECTING STORE LINKS', series, store, true));
if (!Array.isArray(url)) {
url = [url];
}
url.map(x => promises.push(usingResponse(browser, x, async response => {
const text = await response?.text();
if (!text) {
logger.error(Print.message('NO RESPONSE', series, store, true));
return;
}
const docElement = cheerio.load(text).root();
const links = store.linksBuilder!.builder(docElement, series);
addNewLinks(store, links, series);
})));
}
await Promise.all(promises);
}
+18 -12
View File
@@ -1,5 +1,5 @@
import {Config} from '../config';
import {Link} from './model';
import {config} from '../config';
/**
* Returns true if the brand should be checked for stock
@@ -7,27 +7,33 @@ import {Link} from './model';
* @param brand The brand of the GPU
*/
function filterBrand(brand: Link['brand']): boolean {
if (Config.store.showOnlyBrands.length === 0) {
if (config.store.showOnlyBrands.length === 0) {
return true;
}
return Config.store.showOnlyBrands.includes(brand);
return config.store.showOnlyBrands.includes(brand);
}
/**
* Returns true if the model should be checked for stock
*
* @param model The model of the GPU
* @param series The series of the GPU
*/
function filterModel(model: Link['model']): boolean {
if (Config.store.showOnlyModels.length === 0) {
function filterModel(model: Link['model'], series: Link['series']): 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) {
const sanitizedSeries = series.replace(/\s/g, '');
for (const configModelEntry of config.store.showOnlyModels) {
const sanitizedConfigModel = configModelEntry.name.replace(/\s/g, '');
const sanitizedConfigSeries = configModelEntry.series.replace(/\s/g, '');
if (sanitizedConfigSeries ?
sanitizedSeries === sanitizedConfigSeries && sanitizedModel === sanitizedConfigModel :
sanitizedModel === sanitizedConfigModel
) {
return true;
}
}
@@ -40,12 +46,12 @@ function filterModel(model: Link['model']): boolean {
*
* @param series The series of the GPU
*/
function filterSeries(series: Link['series']): boolean {
if (Config.store.showOnlySeries.length === 0) {
export function filterSeries(series: Link['series']): boolean {
if (config.store.showOnlySeries.length === 0) {
return true;
}
return Config.store.showOnlySeries.includes(series);
return config.store.showOnlySeries.includes(series);
}
/**
@@ -56,7 +62,7 @@ function filterSeries(series: Link['series']): boolean {
export function filterStoreLink(link: Link): boolean {
return (
filterBrand(link.brand) &&
filterModel(link.model) &&
filterModel(link.model, link.series) &&
filterSeries(link.series)
);
}
+100 -1
View File
@@ -1,3 +1,83 @@
import {Element, LabelQuery, Pricing} from './model';
import {Page} from 'puppeteer';
import {logger} from '../logger';
export type Selector = {
requireVisible: boolean;
selector: string;
type: 'innerHTML' | 'outerHTML' | 'textContent';
};
function isElementArray(query: LabelQuery): query is Element[] {
return Array.isArray(query) && query.length > 0 && typeof query[0] === 'object';
}
function getQueryAsElementArray(query: LabelQuery, defaultContainer: string): Array<Required<Element>> {
if (isElementArray(query)) {
return query.map(x => ({
container: x.container ?? defaultContainer,
text: x.text
}));
}
if (Array.isArray(query)) {
return [{
container: defaultContainer,
text: query
}];
}
return [{
container: query.container ?? defaultContainer,
text: query.text
}];
}
export async function pageIncludesLabels(page: Page, query: LabelQuery, options: Selector) {
const elementQueries = getQueryAsElementArray(query, options.selector);
const resolved = await Promise.all(elementQueries.map(async query => {
const selector = {...options, selector: query.container};
const contents = await extractPageContents(page, selector) ?? '';
if (!contents) {
return false;
}
logger.debug(contents);
return includesLabels(contents, query.text);
}));
return resolved.includes(true);
}
export async function extractPageContents(page: Page, selector: Selector): Promise<string | null> {
return page.evaluate((options: Selector) => {
// eslint-disable-next-line no-undef
const element: globalThis.HTMLElement | null = document.querySelector(options.selector);
if (!element) {
return null;
}
if (options.requireVisible && !(element.offsetWidth > 0 && element.offsetHeight > 0)) {
return null;
}
switch (options.type) {
case 'innerHTML':
return element.innerHTML;
case 'outerHTML':
return element.outerHTML;
case 'textContent':
return element.textContent;
default:
return 'Error: selector.type is unknown';
}
}, selector);
}
/**
* Checks if DOM has any related text.
*
@@ -6,5 +86,24 @@
*/
export function includesLabels(domText: string, searchLabels: string[]): boolean {
const domTextLowerCase = domText.toLowerCase();
return searchLabels.some(label => domTextLowerCase.includes(label));
return searchLabels.some(label => domTextLowerCase.includes(label.toLowerCase()));
}
export async function cardPrice(page: Page, query: Pricing, max: number, options: Selector) {
if (!max) {
return null;
}
const selector = {...options, selector: query.container};
const cardPrice = await extractPageContents(page, selector);
if (cardPrice) {
const priceSeperator = query.euroFormat ? /\./g : /,/g;
const cardpriceNumber = Number.parseFloat(cardPrice.replace(priceSeperator, '').match(/\d+/g)!.join('.'));
logger.debug(`Raw card price: ${cardPrice} | Limit: ${max}`);
return cardpriceNumber > max ? cardpriceNumber : null;
}
return null;
}
+160 -67
View File
@@ -1,15 +1,20 @@
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 {Link, Store, getStores} from './model';
import {Print, logger} from '../logger';
import {Selector, cardPrice, pageIncludesLabels} from './includes-labels';
import {closePage, delay, getRandomUserAgent, getSleepTime, isStatusCodeInRange} from '../util';
import {config} from '../config';
import {disableBlockerInPage} from '../adblocker';
import {fetchLinks} from './fetch-links';
import {filterStoreLink} from './filter';
import {includesLabels} from './includes-labels';
import open from 'open';
import {processBackoffDelay} from './model/helpers/backoff';
import {sendNotification} from '../notification';
const inStock: Record<string, boolean> = {};
const linkBuilderLastRunTimes: Record<string, number> = {};
/**
* Responsible for looking up information about a each product within
* a `Store`. It's important that we ignore `no-await-in-loop` here
@@ -19,36 +24,82 @@ const inStock: Record<string, boolean> = {};
* @param store Vendor of graphics cards.
*/
async function lookup(browser: Browser, store: Store) {
if (!getStores().has(store.name)) {
return;
}
/* eslint-disable no-await-in-loop */
for (const link of store.links) {
if (!filterStoreLink(link)) {
continue;
}
const page = await browser.newPage();
page.setDefaultNavigationTimeout(Config.page.navigationTimeout);
await page.setUserAgent(Config.page.userAgent);
try {
await lookupCard(browser, store, page, link);
} catch (error) {
Logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`);
if (config.page.inStockWaitTime && inStock[link.url]) {
logger.info(Print.inStockWaiting(link, store, true));
continue;
}
const context = (config.browser.isIncognito ? await browser.createIncognitoBrowserContext() : browser.defaultBrowserContext());
const page = (config.browser.isIncognito ? await context.newPage() : await browser.newPage());
page.setDefaultNavigationTimeout(config.page.timeout);
await page.setUserAgent(getRandomUserAgent());
if (store.disableAdBlocker) {
try {
await disableBlockerInPage(page);
} catch (error) {
logger.error(error);
}
}
let statusCode = 0;
try {
statusCode = await lookupCard(browser, store, page, link);
} catch (error) {
logger.error(`✖ [${store.name}] ${link.brand} ${link.series} ${link.model} - ${error.message as string}`);
const client = await page.target().createCDPSession();
await client.send('Network.clearBrowserCookies');
await client.send('Network.clearBrowserCache');
}
// Must apply backoff before closing the page, e.g. if CloudFlare is
// used to detect bot traffic, it introduces a 5 second page delay
// before redirecting to the next page
await processBackoffDelay(store, link, statusCode);
await closePage(page);
if (config.browser.isIncognito) {
await context.close();
}
}
/* eslint-enable no-await-in-loop */
}
async function lookupCard(browser: Browser, store: Store, page: Page, link: Link) {
async function lookupCard(browser: Browser, store: Store, page: Page, link: Link): Promise<number> {
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 (!response) {
logger.debug(Print.noResponse(link, store, true));
}
if (Config.browser.open) {
const successStatusCodes = store.successStatusCodes ?? [[0, 399]];
const statusCode = response?.status() ?? 0;
if (!isStatusCodeInRange(statusCode, successStatusCodes)) {
if (statusCode === 429) {
logger.warn(Print.rateLimit(link, store, true));
} else {
logger.warn(Print.badStatusCode(link, store, statusCode, true));
}
return statusCode;
}
if (await lookupCardInStock(store, page, link)) {
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 {
@@ -58,77 +109,119 @@ async function lookupCard(browser: Browser, store: Store, page: Page, link: Link
sendNotification(link, store);
if (Config.page.inStockWaitTime) {
inStock[store.name] = true;
if (config.page.inStockWaitTime) {
inStock[link.url] = true;
setTimeout(() => {
inStock[store.name] = false;
}, 1000 * Config.page.inStockWaitTime);
inStock[link.url] = false;
}, 1000 * config.page.inStockWaitTime);
}
if (Config.page.screenshot) {
Logger.debug(' saving screenshot');
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));
return statusCode;
}
async function lookupCardInStock(store: Store, page: Page) {
const stockHandle = await page.$(store.labels.inStock.container);
async function lookupCardInStock(store: Store, page: Page, link: Link) {
const baseOptions: Selector = {
requireVisible: false,
selector: store.labels.container ?? 'body',
type: 'textContent'
};
const visible = await page.evaluate(element => element && element.offsetWidth > 0 && element.offsetHeight > 0, stockHandle);
if (!visible) {
return false;
if (store.labels.inStock) {
const options = {...baseOptions, requireVisible: true, type: 'outerHTML' as const};
if (!await pageIncludesLabels(page, store.labels.inStock, options)) {
logger.info(Print.outOfStock(link, store, true));
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;
if (store.labels.outOfStock) {
if (await pageIncludesLabels(page, store.labels.outOfStock, baseOptions)) {
logger.info(Print.outOfStock(link, store, true));
return false;
}
}
const captchaHandle = await page.$(store.labels.captcha.container);
const captchaContent = await page.evaluate(element => element.textContent, captchaHandle);
if (store.labels.bannedSeller) {
if (await pageIncludesLabels(page, store.labels.bannedSeller, baseOptions)) {
logger.warn(Print.bannedSeller(link, store, true));
return false;
}
}
return includesLabels(captchaContent, store.labels.captcha.text);
if (store.labels.maxPrice) {
let price;
let maxPrice = 0;
switch (link.series) {
case '3070':
price = await cardPrice(page, store.labels.maxPrice, config.store.maxPrice.series['3070'], baseOptions);
maxPrice = config.store.maxPrice.series['3070'];
break;
case '3080':
price = await cardPrice(page, store.labels.maxPrice, config.store.maxPrice.series['3080'], baseOptions);
maxPrice = config.store.maxPrice.series['3080'];
break;
case '3090':
price = await cardPrice(page, store.labels.maxPrice, config.store.maxPrice.series['3090'], baseOptions);
maxPrice = config.store.maxPrice.series['3090'];
break;
default:
break;
}
if (price) {
logger.info(Print.maxPrice(link, store, price, maxPrice, true));
return false;
}
}
if (store.labels.captcha) {
if (await pageIncludesLabels(page, store.labels.captcha, baseOptions)) {
logger.warn(Print.captcha(link, store, true));
await delay(getSleepTime(store));
return false;
}
}
return true;
}
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);
if (!browser.isConnected()) {
logger.debug(`[${store.name}] Ending this loop as browser is disposed...`);
return;
}
const sleepTime = getSleepTime();
Logger.debug(`[${store.name}] Lookup done, next one in ${sleepTime} ms`);
if (store.linksBuilder) {
const lastRunTime = linkBuilderLastRunTimes[store.name] ?? -1;
const ttl = store.linksBuilder.ttl ?? Number.MAX_SAFE_INTEGER;
if (lastRunTime === -1 || (Date.now() - lastRunTime) > ttl) {
try {
await fetchLinks(store, browser);
linkBuilderLastRunTimes[store.name] = Date.now();
} catch (error) {
logger.error(error.message);
}
}
}
logger.debug(`[${store.name}] Starting lookup...`);
try {
await lookup(browser, store);
} catch (error) {
logger.error(error);
}
const sleepTime = getSleepTime(store);
logger.debug(`[${store.name}] Lookup done, next one in ${sleepTime} ms`);
setTimeout(tryLookupAndLoop, sleepTime, browser, store);
}
+53 -1
View File
@@ -9,6 +9,10 @@ export const Adorama: Store = {
inStock: {
container: '.buy-section.purchase',
text: ['add to cart']
},
maxPrice: {
container: '.your-price',
euroFormat: false
}
},
links: [
@@ -68,9 +72,57 @@ export const Adorama: Store = {
},
{
brand: 'pny',
model: 'xlr8 rbg',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.adorama.com/png30801tfxb.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.adorama.com/asrx3080o10g.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.adorama.com/astr3080o10g.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.adorama.com/astrx308010g.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.adorama.com/msig390gxt24.html'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.adorama.com/msig39v3x24c.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.adorama.com/asrtx309024g.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.adorama.com/ast3090o24g.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.adorama.com/asrx3090o24g.html'
}
],
name: 'adorama'
+172
View File
@@ -0,0 +1,172 @@
import {Store} from './store';
export const AlternateNL: Store = {
labels: {
inStock: {
container: '.stockStatus',
text: ['Direct leverbaar']
},
maxPrice: {
container: 'div.price > span',
euroFormat: true
},
outOfStock: {
container: '.stockStatus',
text: ['Levertermijn onbekend', 'pre-order']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.alternate.nl/product/1516616'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.alternate.nl/product/1672756'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.alternate.nl/product/1672345'
},
{
brand: 'asus',
model: 'tuf gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1672251'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.alternate.nl/product/1672753'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.alternate.nl/product/1672343'
},
{
brand: 'asus',
model: 'tuf oc gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1672634'
},
{
brand: 'asus',
model: 'rog strix oc gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1672867'
},
{
brand: 'evga',
model: 'xc3 black gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1673512'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.alternate.nl/product/1672612'
},
{
brand: 'evga',
model: 'xc3 ultra gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1674164'
},
{
brand: 'asus',
model: 'rog strix gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1672868'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3080',
url: 'https://www.alternate.nl/product/1673431'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.alternate.nl/product/1677989'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.alternate.nl/product/1681134'
},
{
brand: 'evga',
model: 'xc3 gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1673520'
},
{
brand: 'gainward',
model: 'phoenix gs',
series: '3080',
url: 'https://www.alternate.nl/product/1673442'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.alternate.nl/product/1673136'
},
{
brand: 'evga',
model: 'ftw3 ultra gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1673524'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.alternate.nl/product/1680168'
},
{
brand: 'evga',
model: 'ftw3 gaming',
series: '3080',
url: 'https://www.alternate.nl/product/1673517'
},
{
brand: 'zotac',
model: 'amp holo',
series: '3080',
url: 'https://www.alternate.nl/product/1677985'
},
{
brand: 'zotac',
model: 'amp extreme holo',
series: '3080',
url: 'https://www.alternate.nl/product/1677982'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.alternate.nl/product/1673137'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.alternate.nl/product/1673134'
}
],
name: 'alternate-nl'
};
+321
View File
@@ -0,0 +1,321 @@
import {Store} from './store';
export const Alternate: Store = {
labels: {
inStock: {
container: '.stockStatus',
text: ['auf lager', 'ware neu eingetroffen', 'in kürze versandfertig', 'ware im zulauf']
},
maxPrice: {
container: 'div.price > span',
euroFormat: true
},
outOfStock: {
container: '.stockStatus',
text: ['liefertermin unbekannt']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.alternate.de/product/1516616'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.alternate.de/product/1672868'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.alternate.de/product/1672867'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.alternate.de/product/1672251'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.alternate.de/product/1672634'
},
{
brand: 'evga',
model: 'ftw3',
series: '3080',
url: 'https://www.alternate.de/product/1673517'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.alternate.de/product/1673524'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.alternate.de/product/1673512'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://www.alternate.de/product/1673520'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.alternate.de/product/1674164'
},
{
brand: 'gainward',
model: 'phantom gs',
series: '3080',
url: 'https://www.alternate.de/product/1688597'
},
{
brand: 'gainward',
model: 'phoenix gs',
series: '3080',
url: 'https://www.alternate.de/product/1673442'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.alternate.de/product/1680168'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3080',
url: 'https://www.alternate.de/product/1680168'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.alternate.de/product/1672756'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.alternate.de/product/1672753'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.alternate.de/product/1681134'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.alternate.de/product/1673136'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.alternate.de/product/1673134'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.alternate.de/product/1673137'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.alternate.de/product/1672343'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.alternate.de/product/1672345'
},
{
brand: 'palit',
model: 'gamerock oc',
series: '3080',
url: 'https://www.alternate.de/product/1688594'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3080',
url: 'https://www.alternate.de/product/1673431'
},
{
brand: 'zotac',
model: 'amp extreme holo',
series: '3080',
url: 'https://www.alternate.de/product/1677982'
},
{
brand: 'zotac',
model: 'amp holo',
series: '3080',
url: 'https://www.alternate.de/product/1677985'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.alternate.de/product/1677989'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.alternate.de/product/1672612'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.alternate.de/product/1672870'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.alternate.de/product/1672872'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.alternate.de/product/1672259'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.alternate.de/product/1672629'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.alternate.de/product/1673530'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.alternate.de/product/1673531'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3090',
url: 'https://www.alternate.de/product/1673521'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://www.alternate.de/product/1673525'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://www.alternate.de/product/1673529'
},
{
brand: 'gainward',
model: 'phoenix gs',
series: '3090',
url: 'https://www.alternate.de/product/1673440'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3090',
url: 'https://www.alternate.de/product/1680670'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.alternate.de/product/1687793'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.alternate.de/product/1672744'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.alternate.de/product/1672749'
},
{
brand: 'gigabyte',
model: 'turbo',
series: '3090',
url: 'https://www.alternate.de/product/1687703'
},
{
brand: 'inno3d',
model: 'gaming x3',
series: '3090',
url: 'https://www.alternate.de/product/1673131'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3090',
url: 'https://www.alternate.de/product/1673135'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3090',
url: 'https://www.alternate.de/product/1673129'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.alternate.de/product/1672341'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.alternate.de/product/1672346'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3090',
url: 'https://www.alternate.de/product/1673434'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.alternate.de/product/1672611'
}
],
name: 'alternate'
};
+48 -2
View File
@@ -9,6 +9,10 @@ export const AmazonCa: Store = {
inStock: {
container: '#desktop_buybox',
text: ['add to cart']
},
maxPrice: {
container: 'span[class*="PriceString"]',
euroFormat: false
}
},
links: [
@@ -24,6 +28,12 @@ export const AmazonCa: Store = {
series: '3080',
url: 'https://www.amazon.ca/dp/B08HR7SV3M'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.amazon.ca/dp/B08HR5SXPS'
},
{
brand: 'evga',
model: 'ftw3',
@@ -78,11 +88,47 @@ export const AmazonCa: Store = {
series: '3080',
url: 'https://www.amazon.ca/dp/B08HH5WF97'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.amazon.ca/dp/B08HJNKT3P'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.amazon.ca/dp/B08HJQ182D'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.amazon.ca/dp/B08HR5SXPS'
series: '3090',
url: 'https://www.amazon.ca/dp/B08HR9D2JS'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.amazon.ca/dp/B08HJRF2CN'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.amazon.ca/dp/B08HJPDJTY'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.amazon.ca/dp/B08HJGNJ81'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.amazon.ca/dp/B08HJLLF7G'
}
],
name: 'amazon-ca'
+181 -26
View File
@@ -1,14 +1,19 @@
import {Store} from './store';
export const AmazonDe: Store = {
backoffStatusCodes: [403, 429, 503],
labels: {
captcha: {
container: 'body',
text: ['geben sie die unten angezeigten zeichen ein']
text: ['geben sie die unten angezeigten zeichen ein', 'geben sie die zeichen unten ein']
},
inStock: {
container: '#desktop_buybox',
text: ['in den einkaufswagen']
},
maxPrice: {
container: 'span[class*="PriceString"]',
euroFormat: true
}
},
links: [
@@ -16,37 +21,31 @@ export const AmazonDe: Store = {
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.com/dp/B07MQ36Z6L'
url: 'https://www.amazon.de/dp/B083JX52VG/'
},
{
brand: 'pny',
model: 'xlr8',
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.amazon.de/dp/B08HBTJMLJ'
url: 'https://www.amazon.de/dp/B08HN7VVLJ'
},
{
brand: 'pny',
model: 'xlr8-rgb',
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.amazon.de/dp/B08HBR7QBM'
url: 'https://www.amazon.de/dp/B08HN6KYS3'
},
{
brand: 'msi',
model: 'gaming x trio',
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.amazon.de/dp/B08HM4V2DH'
url: 'https://www.amazon.de/dp/B08HN37VQK'
},
{
brand: 'evga',
model: 'ftw3 ultra',
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.amazon.de/dp/B08HGYXP4C'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.amazon.de/dp/B08HJ9XFNM'
url: 'https://www.amazon.de/dp/B08HN4DSTC'
},
{
brand: 'evga',
@@ -54,6 +53,12 @@ export const AmazonDe: Store = {
series: '3080',
url: 'https://www.amazon.de/dp/B08HGBYWQ6'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.amazon.de/dp/B08HGYXP4C'
},
{
brand: 'evga',
model: 'xc3',
@@ -67,10 +72,16 @@ export const AmazonDe: Store = {
url: 'https://www.amazon.de/dp/B08HH1BMQQ'
},
{
brand: 'gigabyte',
model: 'gaming oc',
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.amazon.de/dp/B08HLZXHZY'
url: 'https://www.amazon.de/dp/B08HJ9XFNM'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.amazon.de/dp/B08KHLDS72'
},
{
brand: 'gigabyte',
@@ -79,10 +90,28 @@ export const AmazonDe: Store = {
url: 'https://www.amazon.de/dp/B08HHZVZ3N'
},
{
brand: 'asus',
model: 'tuf',
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.amazon.de/dp/B08HN4DSTC'
url: 'https://www.amazon.de/dp/B08HLZXHZY'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.amazon.de/dp/B08KH7RL89'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.amazon.de/dp/B08JD6QPXD'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.amazon.de/dp/B08HM4V2DH'
},
{
brand: 'msi',
@@ -90,11 +119,137 @@ export const AmazonDe: Store = {
series: '3080',
url: 'https://www.amazon.de/dp/B08HM4M621'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3080',
url: 'https://www.amazon.de/dp/B08JCVWTQY'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3080',
url: 'https://www.amazon.de/dp/B08JCKYYL8'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.amazon.de/dp/B08HR1NPPQ'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.amazon.de/dp/B08HN642LY'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.amazon.de/dp/B08HN51T8Q'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.amazon.de/dp/B08HN4FLFJ'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.amazon.de/dp/B08HN5B8FJ'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.amazon.de/dp/B08HGFNPJQ'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.amazon.de/dp/B08J5NMDP7'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.amazon.de/dp/B08HGS1SXH'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://www.amazon.de/dp/B08HGZ4XSZ'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3090',
url: 'https://www.amazon.de/dp/B08HGKQ527'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://www.amazon.de/dp/B08HGTNDL4'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.amazon.de/dp/B08KH7R4FQ'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.amazon.de/dp/B08HJPDJTY'
},
{
brand: 'gigabyte',
model: 'turbo',
series: '3090',
url: 'https://www.amazon.de/dp/B08KHKDTSJ'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.amazon.de/dp/B08HJRF2CN'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.amazon.de/dp/B08HRBW6VB'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.amazon.de/dp/B08HM661YM'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3090',
url: 'https://www.amazon.de/dp/B08JQQ1VD1'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3090',
url: 'https://www.amazon.de/dp/B08HR6ZBYJ'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.amazon.de/dp/B08HJQ182D'
}
],
name: 'amazon-de'
+105
View File
@@ -0,0 +1,105 @@
import {Store} from './store';
export const AmazonEs: Store = {
labels: {
captcha: {
container: 'body',
text: ['introduzca los caracteres que ve a continuación']
},
inStock: {
container: '#desktop_buybox',
text: ['añadir a la cesta']
},
maxPrice: {
container: 'span[class*="PriceString"]',
euroFormat: true
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.es/dp/B083JX52VG/'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.amazon.es/dp/B08HM4V2DH'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.amazon.es/dp/B08HGYXP4C'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.amazon.es/dp/B08HJ9XFNM'
},
{
brand: 'evga',
model: 'ftw3',
series: '3080',
url: 'https://www.amazon.es/dp/B08HGBYWQ6'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://www.amazon.es/dp/B08HGLN78Q'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.amazon.es/dp/B08HH1BMQQ'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.amazon.es/dp/B08HLZXHZY'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.amazon.es/dp/B08HHZVZ3N'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.amazon.es/dp/B08HN37VQK'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.amazon.es/dp/B08HN4DSTC'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.amazon.es/dp/B08HN6KYS3'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.amazon.es/dp/B08HM4M621'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.amazon.es/dp/B08HR1NPPQ'
}
],
name: 'amazon-es'
};
+67
View File
@@ -0,0 +1,67 @@
import {Store} from './store';
export const AmazonNl: Store = {
labels: {
captcha: {
container: 'body',
text: ['voer de karakters in die u hieronder ziet']
},
inStock: {
container: '#availability',
text: ['op voorraad', 'verkrijgbaar vanaf', 'wordt gewoonlijk verzonden binnen', 'nog slechts']
},
maxPrice: {
container: 'span[class*="PriceString"]',
euroFormat: true
},
outOfStock: [
{
container: '#availability',
text: ['tijdelijk niet']
},
{
container: '#outOfStock',
text: ['we weten niet of en wanneer dit item weer op voorraad is']
}
]
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.nl/dp/B083GGYNQ6'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.amazon.nl/dp/B08HM4M621'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.amazon.nl/dp/B08HM4V2DH'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.amazon.nl/dp/B08HJ9XFNM'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.amazon.nl/dp/B08HH1BMQQ'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.amazon.nl/dp/B08HN7VVLJ'
}
],
name: 'amazon-nl'
};
+95
View File
@@ -0,0 +1,95 @@
import {Link, Store} from './store';
import {logger} from '../../logger';
import {parseCard} from './helpers/card';
export const AmazonUk: Store = {
backoffStatusCodes: [403, 429, 503],
labels: {
captcha: {
container: 'body',
text: ['enter the characters you see below']
},
inStock: {
container: '#availability',
text: ['in stock']
},
maxPrice: {
container: 'span[class*="PriceString"]'
},
outOfStock: [
{
container: '#availability',
text: ['out of stock', 'unavailable']
},
{
container: '#backInStock',
text: ['unavailable']
}
]
},
links: [
{
brand: 'test:brand',
cartUrl: 'https://www.amazon.co.uk/gp/aws/cart/add.html?ASIN.1=B081265T5Z&Quantity.1=1',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.co.uk/dp/B081265T5Z/'
}
],
linksBuilder: {
builder: (docElement, series) => {
const productElements = docElement.find('.s-result-list .s-result-item[data-asin]');
const links: Link[] = [];
for (let i = 0; i < productElements.length; i++) {
const productElement = productElements.eq(i);
const asin = productElement.attr()['data-asin'];
if (!asin) {
continue;
}
const url = `https://www.amazon.co.uk/dp/${asin}/`;
const titleElement = productElement.find('.sg-col-inner h2 a.a-text-normal[href] span').first();
const title = titleElement.text().trim();
if (!title || !new RegExp(`RTX.*${series}`, 'i').exec(title)) {
continue;
}
const card = parseCard(title);
if (card) {
links.push({
brand: card.brand as any,
cartUrl: `https://www.amazon.co.uk/gp/aws/cart/add.html?ASIN.1=${asin}&Quantity.1=1`,
model: card.model,
series,
url
});
} else {
logger.error(`Failed to parse card: ${title}`);
}
}
return links;
},
ttl: 300000,
urls: [
{
series: '3080',
url: [
'https://www.amazon.co.uk/s?k=%2B%22RTX+3080%22+-2080+-GTX&i=computers&rh=n%3A430500031%2Cp_n_availability%3A419162031&s=relevancerank&dc&qid=1601675291',
'https://www.amazon.co.uk/s?k=%2B%22RTX+3080%22+-2080+-GTX&i=computers&rh=n%3A430500031%2Cp_n_availability%3A419162031&s=relevancerank&dc&qid=1601675594&page=2'
]
},
{
series: '3090',
url: [
'https://www.amazon.co.uk/s?k=%2B%22RTX+3090%22+-3080+-GTX&i=computers&rh=n%3A430500031%2Cp_n_availability%3A419162031&s=relevancerank&dc&qid=1601675291',
'https://www.amazon.co.uk/s?k=%2B%22RTX+3090%22+-3080+-GTX&i=computers&rh=n%3A430500031%2Cp_n_availability%3A419162031&s=relevancerank&dc&qid=1601675594&page=2'
]
}
]
},
name: 'amazon-uk'
};
+91 -2
View File
@@ -9,104 +9,193 @@ export const Amazon: Store = {
inStock: {
container: '#desktop_buybox',
text: ['add to cart']
},
maxPrice: {
container: 'span[class*="PriceString"]'
}
},
links: [
{
brand: 'test:brand',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B07TDN1SC5&Quantity.1=1',
model: 'test:model',
series: 'test:series',
url: 'https://www.amazon.com/dp/B07MQ36Z6L'
url: 'https://www.amazon.com/dp/B07TDN1SC5'
},
{
brand: 'pny',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HBR7QBM&Quantity.1=1',
model: 'xlr8',
series: '3080',
url: 'https://www.amazon.com/dp/B08HBR7QBM'
},
{
brand: 'pny',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HBTJMLJ&Quantity.1=1',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.amazon.com/dp/B08HBTJMLJ'
},
{
brand: 'msi',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR7SV3M&Quantity.1=1',
model: 'gaming x trio',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR7SV3M'
},
{
brand: 'evga',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR3Y5GQ&Quantity.1=1',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR3Y5GQ'
},
{
brand: 'evga',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR55YB5&Quantity.1=1',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR55YB5'
},
{
brand: 'evga',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR3DPGW&Quantity.1=1',
model: 'ftw3',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR3DPGW'
},
{
brand: 'evga',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR4RJ3Q&Quantity.1=1',
model: 'xc3',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR4RJ3Q'
},
{
brand: 'evga',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR6FMF3&Quantity.1=1',
model: 'xc3 black',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR6FMF3'
},
{
brand: 'gigabyte',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJTH61J&Quantity.1=1',
model: 'gaming oc',
series: '3080',
url: 'https://www.amazon.com/dp/B08HJTH61J'
},
{
brand: 'gigabyte',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJS2JLJ&Quantity.1=1',
model: 'eagle oc',
series: '3080',
url: 'https://www.amazon.com/dp/B08HJS2JLJ'
},
{
brand: 'asus',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HH5WF97&Quantity.1=1',
model: 'tuf oc',
series: '3080',
url: 'https://www.amazon.com/dp/B08HH5WF97'
},
{
brand: 'asus',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HHDP9DW&Quantity.1=1',
model: 'tuf',
series: '3080',
url: 'https://www.amazon.com/dp/B08HHDP9DW'
},
{
brand: 'asus',
model: 'strix',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08J6F174Z&Quantity.1=1',
model: 'rog strix oc',
series: '3080',
url: 'https://www.amazon.com/dp/B08J6F174Z'
},
{
brand: 'asus',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08J6GMWCQ&Quantity.1=1',
model: 'rog strix oc',
series: '3090',
url: 'https://www.amazon.com/dp/B08J6GMWCQ'
},
{
brand: 'msi',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR5SXPS&Quantity.1=1',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.amazon.com/dp/B08HR5SXPS'
},
{
brand: 'zotac',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJNKT3P&Quantity.1=1',
model: 'trinity',
series: '3080',
url: 'https://www.amazon.com/dp/B08HJNKT3P'
},
{
brand: 'zotac',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJQ182D&Quantity.1=1',
model: 'trinity',
series: '3090',
url: 'https://www.amazon.com/dp/B08HJQ182D'
},
{
brand: 'pny',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HBQWBHH&Quantity.1=1',
model: 'xlr8',
series: '3090',
url: 'https://www.amazon.com/dp/B08HBQWBHH'
},
{
brand: 'pny',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HBVX53D&Quantity.1=1',
model: 'xlr8',
series: '3090',
url: 'https://www.amazon.com/dp/B08HBVX53D'
},
{
brand: 'msi',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HRBW6VB&Quantity.1=1',
model: 'gaming x trio',
series: '3090',
url: 'https://www.amazon.com/dp/B08HRBW6VB'
},
{
brand: 'msi',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HR9D2JS&Quantity.1=1',
model: 'ventus 3x',
series: '3090',
url: 'https://www.amazon.com/dp/B08HR9D2JS'
},
{
brand: 'gigabyte',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJRF2CN&Quantity.1=1',
model: 'gaming oc',
series: '3090',
url: 'https://www.amazon.com/dp/B08HJRF2CN'
},
{
brand: 'gigabyte',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJPDJTY&Quantity.1=1',
model: 'eagle oc',
series: '3090',
url: 'https://www.amazon.com/dp/B08HJPDJTY'
},
{
brand: 'asus',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJGNJ81&Quantity.1=1',
model: 'tuf oc',
series: '3090',
url: 'https://www.amazon.com/dp/B08HJGNJ81'
},
{
brand: 'asus',
cartUrl: 'https://www.amazon.com/gp/aws/cart/add.html?ASIN.1=B08HJLLF7G&Quantity.1=1',
model: 'tuf oc',
series: '3090',
url: 'https://www.amazon.com/dp/B08HJLLF7G'
}
],
name: 'amazon'
+46
View File
@@ -0,0 +1,46 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Aria: Store = {
labels: {
inStock: {
container: '#addQuantity',
text: ['add to shopping basket']
},
maxPrice: {
container: '.priceBig',
euroFormat: false // Note: Aria uses non-euroFromat as price seperator
},
outOfStock: {
container: '.fBox',
text: ['out of stock', 'there is currently no stock of this item']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.aria.co.uk/Products/Components/Graphics+Cards/NVIDIA+GeForce/GeForce+RTX+2060+Super/Gigabyte+NVIDIA+GeForce+RTX+2060+SUPER+8GB+WINDFORCE+OC+Turing+Graphics+Card+%2B+RTX+Bundle%21?productId=71541'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '#productListingInner .listTable .listTableTr',
sitePrefix: 'https://www.aria.co.uk',
titleSelector: 'strong > a[href]'
}),
urls: [
{
series: '3080',
url: 'https://www.aria.co.uk/Products/Components/Graphics+Cards/NVIDIA+GeForce/GeForce+RTX+3080'
},
{
series: '3090',
url: 'https://www.aria.co.uk/Products/Components/Graphics+Cards/NVIDIA+GeForce/GeForce+RTX+3090'
}
]
},
name: 'aria',
waitUntil: 'domcontentloaded'
};
+67
View File
@@ -0,0 +1,67 @@
import {Store} from './store';
export const AsusDe: Store = {
labels: {
inStock: {
container: '.buybox--button',
text: ['in den warenkorb']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2766/asus-rog-strix-rtx2060s-o8g-evo-v2-gaming'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2828/asus-rog-strix-rtx3080-10g-gaming'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2829/asus-rog-strix-rtx3080-o10g-gaming'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2824/asus-tuf-rtx3080-10g-gaming'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2825/asus-tuf-rtx3080-o10g-gaming'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2826/asus-rog-strix-rtx3090-24g-gaming'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2827/asus-rog-strix-rtx3090-o24g-gaming'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2822/asus-tuf-rtx3090-24g-gaming'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://webshop.asus.com/de/komponenten/grafikkarten/nvidia-serie/2823/asus-tuf-rtx3090-o24g-gaming'
}
],
name: 'asus-de'
};
+17 -4
View File
@@ -12,21 +12,34 @@ export const Asus: Store = {
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://store.asus.com/us/item/202003AM280000002/'
url: 'https://store.asus.com/us/item/202003AM280000002'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://store.asus.com/us/item/202009AM160000001/'
url: 'https://store.asus.com/us/item/202009AM160000001'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://store.asus.com/us/item/202009AM150000004/'
url: 'https://store.asus.com/us/item/202009AM150000004'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://store.asus.com/us/item/202009AM150000003'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://store.asus.com/us/item/202009AM290000002'
}
],
name: 'asus'
name: 'asus',
successStatusCodes: [[0, 399], 404]
};
+165
View File
@@ -0,0 +1,165 @@
import {Store} from './store';
export const Azerty: Store = {
labels: {
inStock: {
container: '.orderdelay',
text: ['Volgende werkdag in huis', '1 werkdag', '2-3 werkdagen']
},
maxPrice: {
container: '.mod_article .price',
euroFormat: true
},
outOfStock: {
container: '.orderdelay',
text: ['Onbekend', 'meer dan 10 werkdagen', 'Pre-order']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://azerty.nl/product/gigabyte/3756757/geforce-rtx-2060-oc-6g-grafische-kaart-geforce-rtx-2060'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3080',
url: 'https://azerty.nl/product/gigabyte/4349658/aorus-geforce-rtx-3080-xtreme-10g-grafische-kaart-gf-rtx-3080'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://azerty.nl/product/msi/4346262/geforce-rtx-3080-ventus-3x-10g-oc-grafische-kaart-rtx-3080'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://azerty.nl/product/asus/4346679/tuf-gaming-geforce-rtx-3080-grafische-kaart-gf-rtx-3080'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://azerty.nl/product/msi/4346263/geforce-rtx-3080-gaming-x-trio-10g-grafische-kaart-rtx-3080'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://azerty.nl/product/asus/4348176/tuf-gaming-geforce-rtx-3080-oc-grafische-kaart-gf-rtx-3080'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://azerty.nl/product/asus/4348174/rog-strix-geforce-rtx-3080-oc-grafische-kaart-gf-rtx-3080'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://azerty.nl/product/zotac/4352301/gaming-geforce-rtx-3080-trinity-oc-grafische-kaart-gf-rtx-3080'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://azerty.nl/product/gigabyte/4349651/aorus-geforce-rtx-3080-master-10g-grafische-kaart-gf-rtx-3080'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://azerty.nl/product/pny/4342269/geforce-rtx-3080-xlr8-gaming-revel-epic-x-rgb-triple-fan-gaming-edition-grafische-kaart-gf-rtx-3080'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3070',
url: 'https://azerty.nl/product/asus/4363892/rog-strix-gaming-geforce-rtx-3070-o8g-videokaart-8-gb-gddr6'
},
{
brand: 'asus',
model: 'rog strix',
series: '3070',
url: 'https://azerty.nl/product/asus/4373096/rog-strix-gaming-geforce-rtx-3070-8g-videokaart-8-gb-gddr6'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3070',
url: 'https://azerty.nl/product/asus/4363910/tuf-gaming-geforce-rtx-3070-oc-videokaart-8-gb-gddr6'
},
{
brand: 'asus',
model: 'tuf',
series: '3070',
url: 'https://azerty.nl/product/asus/4373099/tuf-gaming-geforce-rtx-3070-videokaart-8-gb-gddr6'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3070',
url: 'https://azerty.nl/product/inno3d/4369725/geforce-rtx-3070-ichill-x4-videokaart-8-gb-ddr6'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3070',
url: 'https://azerty.nl/product/inno3d/4369726/geforce-rtx-3070-ichill-x3-videokaart-8-gb-ddr6'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3070',
url: 'https://azerty.nl/product/msi/4374747/geforce-rtx-3070-ventus-3x-oc-videokaart-8-gb-gddr6'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3070',
url: 'https://azerty.nl/product/msi/4365398/geforce-rtx-3070-gaming-x-trio-videokaart-8-gb-gddr6'
},
{
brand: 'pny',
model: 'dual fan',
series: '3070',
url: 'https://azerty.nl/product/pny/4342270/uprising-dual-fan-geforce-rtx-3070'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3070',
url: 'https://azerty.nl/product/pny/4342271/epic-x-rgb-geforce-rtx-3070-triple-fan-gaming-edition-xlr8--videokaart-8-gb-gddr6x'
},
{
brand: 'evga',
model: 'ftw3',
series: '3070',
url: 'https://azerty.nl/product/evga/4377247/geforce-rtx-3070-ftw3-videokaart-8-gb-gddr6'
},
{
brand: 'evga',
model: 'xc3',
series: '3070',
url: 'https://azerty.nl/product/evga/4377248/xc3-geforce-rtx-3070-xc3-videokaart-8-gb-gddr6'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3070',
url: 'https://azerty.nl/product/evga/4377250/ftw3-ultra-geforce-rtx-3070-videokaart-8-gb-gddr6'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3070',
url: 'https://azerty.nl/product/evga/4377252/xc3-black-geforce-rtx-3070-videokaart-8-gb-gddr6'
}
],
name: 'azerty'
};
+61 -2
View File
@@ -1,10 +1,15 @@
import {Store} from './store';
export const BAndH: Store = {
backoffStatusCodes: [403, 429],
labels: {
inStock: {
container: 'div[data-selenium="addToCartSection"]',
text: ['add to cart']
},
maxPrice: {
container: 'div[data-selenium="pricingPrice"]',
euroFormat: false
}
},
links: [
@@ -54,15 +59,69 @@ export const BAndH: Store = {
},
{
brand: 'msi',
model: 'gaming x trio - duplicate',
model: 'gaming x trio',
series: '3080',
url: 'https://www.bhphotovideo.com/c/product/1593645-REG/msi_geforce_rtx_3080_gaming.html'
},
{
brand: 'msi',
model: 'ventus 3x oc - duplicate',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1592970-REG/zotac_zt_a30900d_10p_gaming_geforce_rtx_3090.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1593647-REG/msi_geforce_rtx_3090_gaming.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1593994-REG/msi_g3090gxt24_geforce_rtx_3090_gaming.html'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1593648-REG/msi_geforce_rtx_3090_ventus.html'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1593995-REG/msi_g3090v3x24c_geforce_rtx_3090_ventus.html'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1593334-REG/gigabyte_gv_n3090eagle_oc_24gd_geforce_rtx_3090_eagle.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1593335-REG/gigabyte_gv_n3090gaming_oc_24gd_geforce_rtx3090_gaming_oc.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1594454-REG/asus_90yv0fd0_m0am00_tuf_gaming_geforce_rtx.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.bhphotovideo.com/c/product/1594451-REG/asus_90yv0fd1_m0am00_tuf_gaming_geforce_rtx.html'
}
],
name: 'bandh'
+28
View File
@@ -5,6 +5,10 @@ export const BestBuyCa: Store = {
inStock: {
container: '#root',
text: ['available online']
},
maxPrice: {
container: 'div[class^="productPricingContainer"] span[class^="screenReaderOnly_"',
euroFormat: false
}
},
links: [
@@ -67,6 +71,30 @@ export const BestBuyCa: Store = {
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'
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3070',
url: 'https://www.bestbuy.ca/en-ca/product/msi-nvidia-geforce-rtx-3070-ventus-3x-oc-8gb-gddr6x-video-card/15038016?intl=nosplash'
},
{
brand: 'zotac',
model: 'twin edge oc',
series: '3070',
url: 'https://www.bestbuy.ca/en-ca/product/zotac-nvidia-geforce-rtx-3070-twin-edge-oc-8gb-gddr6x-video-card/15000078?intl=nosplash'
},
{
brand: 'zotac',
model: 'twin edge',
series: '3070',
url: 'https://www.bestbuy.ca/en-ca/product/zotac-nvidia-geforce-rtx-3070-twin-edge-8gb-gddr6x-video-card/15000079?intl=nosplash'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3070',
url: 'https://www.bestbuy.ca/en-ca/product/nvidia-geforce-rtx-3070-8gb-gddr6-video-card-only-at-best-buy/15078017?intl=nosplash'
}
],
name: 'bestbuy-ca',
+137 -8
View File
@@ -3,8 +3,12 @@ import {Store} from './store';
export const BestBuy: Store = {
labels: {
inStock: {
container: '.v-m-bottom-g',
container: '[data-sticky-media-gallery] .fulfillment-add-to-cart-button',
text: ['add to cart']
},
maxPrice: {
container: '[data-sticky-media-gallery] .priceView-price .priceView-hero-price span',
euroFormat: false
}
},
links: [
@@ -12,7 +16,14 @@ export const BestBuy: Store = {
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'
url: 'https://www.bestbuy.com/site/evga-ko-ultra-gaming-nvidia-geforce-rtx-2060-6gb-gddr6-pci-express-3-0-graphics-card-black-gray/6403801.p?skuId=6403801&intl=nosplash'
},
{
brand: 'nvidia',
cartUrl: 'https://api.bestbuy.com/click/-/6429442/cart',
model: 'founders edition',
series: '3070',
url: 'https://www.bestbuy.com/site/nvidia-geforce-rtx-3070-8gb-gddr6-pci-express-4-0-graphics-card-dark-platinum-and-black/6429442.p?skuId=6429442&intl=nosplash'
},
{
brand: 'nvidia',
@@ -28,6 +39,13 @@ export const BestBuy: Store = {
series: '3080',
url: 'https://www.bestbuy.com/site/asus-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-strix-graphics-card-black/6432445.p?skuId=6432445&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6439300/cart',
model: 'xc3 black',
series: '3070',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3070-xc3-black-gaming-8gb-gddr6x-pci-express-4-0-graphics-card/6439300.p?skuId=6439300&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6432399/cart',
@@ -35,6 +53,13 @@ export const BestBuy: Store = {
series: '3080',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6432399.p?skuId=6432399&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6436194/cart',
model: 'xc3',
series: '3080',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6436194.p?skuId=6436194&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6432400/cart',
@@ -42,6 +67,34 @@ export const BestBuy: Store = {
series: '3080',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6432400.p?skuId=6432400&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6436196/cart',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6436196.p?skuId=6436196&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6436191/cart',
model: 'ftw3',
series: '3080',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3080-10gb-gddr6x-pci-express-4-0-graphics-card/6436191.p?skuId=6436191&intl=nosplash'
},
{
brand: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6437912/cart',
model: 'eagle',
series: '3070',
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3070-8g-gddr6-pci-express-4-0-graphics-card-black/6437912.p?skuId=6437912&intl=nosplash'
},
{
brand: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6437909/cart',
model: 'gaming oc',
series: '3070',
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3070-8g-gddr6-pci-express-4-0-graphics-card-black/6437909.p?skuId=6437909&intl=nosplash'
},
{
brand: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6430620/cart',
@@ -56,6 +109,20 @@ export const BestBuy: Store = {
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: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6436219/cart',
model: 'vision oc',
series: '3080',
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-white/6436219.p?skuId=6436219&intl=nosplash'
},
{
brand: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6436223/cart',
model: 'aorus master',
series: '3080',
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3080-10g-gddr6x-pci-express-4-0-graphics-card-black/6436223.p?skuId=6436223&intl=nosplash'
},
{
brand: 'msi',
cartUrl: 'https://api.bestbuy.com/click/-/6430175/cart',
@@ -63,41 +130,103 @@ export const BestBuy: Store = {
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: 'pny',
cartUrl: 'https://api.bestbuy.com/click/-/6432654/cart',
model: 'dual fan',
series: '3070',
url: 'https://www.bestbuy.com/site/pny-geforce-rtx-3070-8gb-dual-fan-graphics-card/6432654.p?skuId=6432654&intl=nosplash'
},
{
brand: 'pny',
cartUrl: 'https://api.bestbuy.com/click/-/6432653/cart',
model: 'xlr8 rgb',
series: '3070',
url: 'https://www.bestbuy.com/site/pny-geforce-rtx-3070-8gb-xlr8-gaming-epic-x-rgb-triple-fan-graphics-card/6432653.p?skuId=6432653&intl=nosplash'
},
{
brand: 'pny',
cartUrl: 'https://api.bestbuy.com/click/-/6432655/cart',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.bestbuy.com/site/pny-geforce-rtx-3080-10gb-xlr8-gaming-epic-x-rgb-triple-fan-graphics-card/6432655.p?skuId=6432655&intl=nosplash'
},
{
brand: 'pny',
cartUrl: 'https://api.bestbuy.com/click/-/6432658/cart',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.bestbuy.com/site/pny-geforce-rtx-3080-10gb-xlr8-gaming-epic-x-rgb-triple-fan-graphics-card/6432658.p?skuId=6432658&intl=nosplash'
},
{
brand: 'nvidia',
cartUrl: 'https://api.bestbuy.com/click/-/6429434/cart',
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'
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&intl=nosplash'
},
{
brand: 'asus',
cartUrl: 'https://api.bestbuy.com/click/-/6432447/cart',
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'
url: 'https://www.bestbuy.com/site/asus-geforce-rtx-3090-24gb-gddr6x-pci-express-4-0-strix-graphics-card-black/6432447.p?skuId=6432447&intl=nosplash'
},
{
brand: 'asus',
cartUrl: 'https://api.bestbuy.com/click/-/6432446/cart',
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'
url: 'https://www.bestbuy.com/site/asus-tuf-rtx-3090-24gb-gddr6x-pci-express-4-0-graphics-card-black/6432446.p?skuId=6432446&intl=nosplash'
},
{
brand: 'msi',
cartUrl: 'https://api.bestbuy.com/click/-/6430215/cart',
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'
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&intl=nosplash'
},
{
brand: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6430623/cart',
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'
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3090-24g-gddr6x-pci-express-4-0-graphics-card-black/6430623.p?skuId=6430623&intl=nosplash'
},
{
brand: 'gigabyte',
cartUrl: 'https://api.bestbuy.com/click/-/6430624/cart',
model: 'eagle',
series: '3090',
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3090-24g-gddr6x-pci-express-4-0-graphics-card-black/6430624.p?skuId=6430624'
url: 'https://www.bestbuy.com/site/gigabyte-geforce-rtx-3090-24g-gddr6x-pci-express-4-0-graphics-card-black/6430624.p?skuId=6430624&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6434363/cart',
model: 'xc3',
series: '3090',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3090-24gb-gddr6x-pci-express-4-0-graphics-card/6434363.p?skuId=6434363&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6436193/cart',
model: 'ftw3',
series: '3090',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3090-24gb-gddr6x-pci-express-4-0-graphics-card/6436193.p?skuId=6436193&intl=nosplash'
},
{
brand: 'evga',
cartUrl: 'https://api.bestbuy.com/click/-/6436192/cart',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.bestbuy.com/site/evga-geforce-rtx-3090-24gb-gddr6x-pci-express-4-0-graphics-card/6436192.p?skuId=6436192&intl=nosplash'
},
{
brand: 'pny',
cartUrl: 'https://api.bestbuy.com/click/-/6432657/cart',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.bestbuy.com/site/pny-geforce-rtx-3090-24gb-xlr8-gaming-epic-x-rgb-triple-fan-graphics-card/6432657.p?skuId=6432657&intl=nosplash'
}
],
name: 'bestbuy'
+49
View File
@@ -0,0 +1,49 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Box: Store = {
labels: {
inStock: {
container: '#divBuyButton',
text: ['add to basket']
},
maxPrice: {
container: '.p-right-wrapper .pq-price',
euroFormat: false // Note: Box uses non-euroFromat as price seperator
},
outOfStock: {
text: ['request stock alert', 'coming soon']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.box.co.uk/Gigabyte-GeForce-RTX-2080-Super-8GB-Wind_2724554.html'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '.products-right .p-list',
sitePrefix: 'https://www.box.co.uk',
titleSelector: '.p-list-section > h3 > a[href]'
}),
urls: [
{
series: '3070',
url: 'https://www.box.co.uk/rtx-3070-graphics-cards'
},
{
series: '3080',
url: 'https://www.box.co.uk/rtx-3080-graphics-cards'
},
{
series: '3090',
url: 'https://www.box.co.uk/rtx-3090-graphics-cards'
}
]
},
name: 'box',
waitUntil: 'domcontentloaded'
};
+275
View File
@@ -0,0 +1,275 @@
import {Store} from './store';
export const Caseking: Store = {
backoffStatusCodes: [403, 429],
labels: {
inStock: {
container: '.delivery_container',
text: ['lagernd', 'im zulauf', 'ab']
},
maxPrice: {
container: '#buybox .article_details_price',
euroFormat: true
},
outOfStock: {
container: '.delivery_container',
text: ['unbekannt']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.caseking.de/evga-geforce-rtx-2060-super-xc-ultra-gaming-8192-mb-gddr6-gcev-385.html'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.caseking.de/asus-geforce-rtx-3080-rog-strix-10g-10240-mb-gddr6x-gcas-400.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.caseking.de/asus-geforce-rtx-3080-rog-strix-o10g-10240-mb-gddr6x-gcas-399.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.caseking.de/asus-geforce-rtx-3080-tuf-gaming-10g-10240-mb-gddr6x-gcas-394.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.caseking.de/asus-geforce-rtx-3080-tuf-gaming-o10g-10240-mb-gddr6x-gcas-396.html'
},
{
brand: 'evga',
model: 'ftw3',
series: '3080',
url: 'https://www.caseking.de/evga-geforce-rtx-3080-ftw3-gaming-10240-mb-gddr6x-gcev-416.html'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.caseking.de/evga-geforce-rtx-3080-ftw3-ultra-gaming-10240-mb-gddr6x-gcev-417.html'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://www.caseking.de/evga-geforce-rtx-3080-xc3-gaming-10240-mb-gddr6x-gcev-415.html'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.caseking.de/evga-geforce-rtx-3080-xc3-black-gaming-10240-mb-gddr6x-gcev-414.html'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.caseking.de/evga-geforce-rtx-3080-xc3-ultra-gaming-10240-mb-gddr6x-gcev-423.html'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.caseking.de/gigabyte-aorus-geforce-rtx-3080-master-10g-10240-mb-gddr6x-gcgb-331.html'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.caseking.de/gigabyte-geforce-rtx-3080-eagle-oc-10g-10240-mb-gddr6x-gcgb-326.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.caseking.de/gigabyte-geforce-rtx-3080-gaming-oc-10g-10240-mb-gddr6x-gcgb-327.html'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.caseking.de/gigabyte-geforce-rtx-3080-vision-oc-10g-10240-mb-gddr6x-gcgb-332.html'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.caseking.de/inno3d-geforce-rtx-3080-ichill-x3-10240-mb-gddr6x-gci3-170.html'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.caseking.de/inno3d-geforce-rtx-3080-ichill-x4-10240-mb-gddr6x-gci3-169.html'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.caseking.de/inno3d-geforce-rtx-3080-twin-x2-oc-10240-mb-gddr6x-gci3-171.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.caseking.de/msi-geforce-rtx-3080-gaming-x-trio-10g-10240-mb-gddr6x-gcmc-248.html'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.caseking.de/msi-geforce-rtx-3080-ventus-3x-10g-oc-10240-mb-gddr6x-gcmc-247.html'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.caseking.de/pny-geforce-rtx-3080-xlr8-gaming-epic-x-rgb-10240-mb-gddr6x-gcpn-075.html'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.caseking.de/pny-geforce-rtx-3080-xlr8-gaming-revel-epic-x-rgb-10240-mb-gddr6x-gcpn-076.html'
},
{
brand: 'zotac',
model: 'amp holo',
series: '3080',
url: 'https://www.caseking.de/zotac-gaming-geforce-rtx-3080-amp-holo-10240-mb-gddr6x-gczt-166.html'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.caseking.de/zotac-gaming-geforce-rtx-3080-trinity-oc-10240-mb-gddr6x-gczt-167.html'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.caseking.de/zotac-gaming-geforce-rtx-3080-trinity-10240-mb-gddr6x-gczt-163.html'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.caseking.de/asus-geforce-rtx-3090-rog-strix-24g-24576-mb-gddr6x-gcas-397.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.caseking.de/asus-geforce-rtx-3090-rog-strix-o24g-24576-mb-gddr6x-gcas-398.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.caseking.de/asus-geforce-rtx-3090-tuf-gaming-24g-24576-mb-gddr6x-gcas-393.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.caseking.de/asus-geforce-rtx-3090-tuf-gaming-o24g-24576-mb-gddr6x-gcas-395.html'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.caseking.de/evga-geforce-rtx-3090-ftw3-gaming-24576-mb-gddr6x-gcev-421.html'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.caseking.de/evga-geforce-rtx-3090-ftw3-ultra-gaming-24576-mb-gddr6x-gcev-422.html'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://www.caseking.de/evga-geforce-rtx-3090-xc3-gaming-24576-mb-gddr6x-gcev-419.html'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3090',
url: 'https://www.caseking.de/evga-geforce-rtx-3090-xc3-black-gaming-24576-mb-gddr6x-gcev-418.html'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://www.caseking.de/evga-geforce-rtx-3090-xc3-ultra-gaming-24576-mb-gddr6x-gcev-420.html'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.caseking.de/gigabyte-geforce-rtx-3090-eagle-oc-24g-24576-mb-gddr6x-gcgb-329.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.caseking.de/gigabyte-geforce-rtx-3090-gaming-oc-24g-24576-mb-gddr6x-gcgb-328.html'
},
{
brand: 'inno3d',
model: 'gaming x3',
series: '3090',
url: 'https://www.caseking.de/inno3d-geforce-rtx-3090-gaming-x3-24576-mb-gddr6x-gci3-172.html'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3090',
url: 'https://www.caseking.de/inno3d-geforce-rtx-3090-ichill-x3-24576-mb-gddr6x-gci3-168.html'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3090',
url: 'https://www.caseking.de/inno3d-geforce-rtx-3090-ichill-x4-24576-mb-gddr6x-gci3-167.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.caseking.de/msi-geforce-rtx-3090-gaming-x-trio-24g-24576-mb-gddr6x-gcmc-244.html'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.caseking.de/pny-geforce-rtx-3090-xlr8-gaming-epic-x-rgb-24576-mb-gddr6x-gcpn-073.html'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.caseking.de/pny-geforce-rtx-3090-xlr8-gaming-revel-epic-x-rgb-24576-mb-gddr6x-gcpn-074.html'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.caseking.de/zotac-gaming-geforce-rtx-3090-trinity-24576-mb-gddr6x-gczt-162.html'
}
],
name: 'caseking'
};
+51
View File
@@ -0,0 +1,51 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Ccl: Store = {
labels: {
inStock: {
container: '#pnlAddToBasket',
text: ['add to basket']
},
maxPrice: {
container: '#pnlPriceText > p',
euroFormat: false // Note: CCL uses non-euroFromat as price seperator
},
outOfStock: {
container: '#pnlSoldOut',
text: ['sold out', 'coming soon']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.cclonline.com/product/296443/RTX-2060-SUPER-VENTUS-GP-OC/Graphics-Cards/MSI-GeForce-RTX-2060-SUPER-VENTUS-GP-OC-8GB-Overclocked-Graphics-Card/VGA5671/'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '.productListingContainerOuter .productList',
sitePrefix: 'https://www.cclonline.com',
titleAttribute: 'title',
titleSelector: '.productList_Detail a[title]'
}),
urls: [
{
series: '3070',
url: 'https://www.cclonline.com/category/430/PC-Components/Graphics-Cards/GeForce-RTX-3070-Graphics-Cards/'
},
{
series: '3080',
url: 'https://www.cclonline.com/category/430/PC-Components/Graphics-Cards/GeForce-RTX-3080-Graphics-Cards/'
},
{
series: '3090',
url: 'https://www.cclonline.com/category/430/PC-Components/Graphics-Cards/GeForce-RTX-3090-Graphics-Cards/'
}
]
},
name: 'ccl',
waitUntil: 'domcontentloaded'
};
+455
View File
@@ -0,0 +1,455 @@
import {Store} from './store';
export const Computeruniverse: Store = {
labels: {
inStock: {
container: '.availability',
text: ['bestellartikel', 'auf lager und sofort lieferbar', 'kurzfristig verfügbar']
},
maxPrice: {
container: '.product-price',
euroFormat: true
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.computeruniverse.net/de/gainward-geforce-gtx-1660-super-ghost-6-gb-high-end-grafikkarte'
},
{
brand: 'asus',
model: 'dual',
series: '3070',
url: 'https://www.computeruniverse.net/de/asus-geforce-rtx3070-dual-rtx3070-8g-8-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'dual oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/asus-geforce-rtx3070-dual-oc-dual-rtx3070-o8g-8-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'rog strix',
series: '3070',
url: 'https://www.computeruniverse.net/de/asus-rog-strix-geforce-rtx3070-gaming-8-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/asus-rog-strix-geforce-rtx3070-gaming-oc-8-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3070',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3070-xc3-black'
},
{
brand: 'gainward',
model: 'phoenix',
series: '3070',
url: 'https://www.computeruniverse.net/de/gainward-geforce-rtx3070-phoenix-8-gb-enthusiast-grafikkarte'
},
{
brand: 'gainward',
model: 'phoenix gs',
series: '3070',
url: 'https://www.computeruniverse.net/de/gainward-geforce-rtx3070-phoenix-gs-8-gb-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'eagle',
series: '3070',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3070-eagle-8-gb-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3070-eagle-oc-8-gb-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3070-gaming-oc-8-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3070',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3070-ichill-x3'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3070',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3070-ichill-x4'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3070',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3070-gaming-x-trio-8-gb-enthusiast-grafikkarte'
},
{
brand: 'msi',
model: 'ventus 2x oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3070-ventus-2x-oc-8-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3070-ventus-3x-oc-8-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3070',
url: 'https://www.computeruniverse.net/de/palit-geforce-rtx3070-gaming-pro-8-gb'
},
{
brand: 'pny',
model: 'dual fan',
series: '3070',
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx3070-m-dual-8-gb'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3070',
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx3070-xlr8-gaming-epic-x-rgb-p-8-gb-enthusiast-grafikkarte'
},
{
brand: 'zotac',
model: 'twin edge oc',
series: '3070',
url: 'https://www.computeruniverse.net/de/zotac-gaming-geforce-rtx3070-twin-edge-oc-8-gb-oc'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.computeruniverse.net/de/asus-rog-strix-geforce-rtx3080-gaming-10-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/asus-rog-strix-geforce-rtx3080-gaming-oc-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.computeruniverse.net/de/asus-tuf-geforce-rtx3080-gaming-tuf-rtx3080-10g-gaming-10-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/asus-tuf-geforce-rtx3080-gaming-oc-tuf-rtx3080-o10g-gaming-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'ftw3',
series: '3080',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3080-ftw3-10-gb-high-end-grafikkarte'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3080-ultra-ftw3-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3080-xc3-10-gb-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3080-xc3-black-10-gb-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3080-xc3-ultra-10-gb-enthusiast-grafikkarte'
},
{
brand: 'gainward',
model: 'phoenix',
series: '3080',
url: 'https://www.computeruniverse.net/de/gainward-geforce-rtx3080-phoenix-10-gb-enthusiast-grafikkarte'
},
{
brand: 'gainward',
model: 'phoenix gs',
series: '3080',
url: 'https://www.computeruniverse.net/de/gainward-geforce-rtx3080-phoenix-gs-10-gb-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.computeruniverse.net/de/gigabyte-aorus-geforce-rtx3080-master-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3080-eagle-oc-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3080-gaming-oc-10-gb-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3080-vision-oc-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3080-ichill-x3-10-gb-enthusiast-grafikkarte'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3080-ichill-x4-10-gb-enthusiast-grafikkarte'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3080-twin-x2-oc-10-gb-enthusiast-grafikkarte'
},
{
brand: 'kfa2',
model: 'sg oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/kfa-geforce-rtx3080-sg-oc-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3080-gaming-x-trio-10-gb-enthusiast-grafikkarte'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3080-ventus-3x-oc-10-gb-enthusiast-grafikkarte'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3080',
url: 'https://www.computeruniverse.net/de/palit-geforce-rtx3080-gamingpro-10-gb-enthusiast-grafikkarte'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/palit-geforce-rtx3080-gamingpro-oc-10-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx-3080-xlr8-gaming-epic-x-rgb-p-10-gb-enthusiast-grafikkarte'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx3080-xlr8-gaming-epic-x-rgb-m-10-gb-enthusiast-grafikkarte'
},
{
brand: 'zotac',
model: 'amp holo',
series: '3080',
url: 'https://www.computeruniverse.net/de/zotac-geforce-rtx3080-amp-holo-10-gb-enthusiast-grafikkarte'
},
{
brand: 'zotac',
model: 'amp extreme holo',
series: '3080',
url: 'https://www.computeruniverse.net/de/zotac-geforce-rtx3080-amp-extreme-holo-10-gb-enthusiast-grafikkarte'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.computeruniverse.net/de/zotac-gaming-geforce-rtx3080-trinity-10-gb-enthusiast-grafikkarte'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.computeruniverse.net/de/zotac-gaming-geforce-rtx3080-trinity-oc-10-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.computeruniverse.net/de/asus-rog-strix-geforce-rtx3090-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/asus-rog-strix-geforce-rtx3090-gaming-oc-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.computeruniverse.net/de/asus-tuf-geforce-rtx3090-gaming-tuf-rtx3090-24g-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/asus-tuf-geforce-rtx3090-gaming-oc-tuf-rtx3090-o24g-gaming-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3090-ftw3-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3090-ftw3-ultra-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3090-xc3-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3090',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3090-xc3-black-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://www.computeruniverse.net/de/evga-geforce-rtx3090-xc3-ultra-gaming-24-gb-enthusiast-grafikkarte'
},
{
brand: 'gainward',
model: 'phoenix',
series: '3090',
url: 'https://www.computeruniverse.net/de/gainward-geforce-rtx3090-phoenix-24-gb-enthusiast-grafikkarte'
},
{
brand: 'gainward',
model: 'phoenix gs oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/gainward-geforce-rtx3090-phoenix-gs-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.computeruniverse.net/de/gigabyte-aorus-geforce-rtx3090-master-24gb'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3090-eagle-oc-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/gigabyte-geforce-rtx3090-gaming-oc-24-gb-oc'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3090',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3090-ichill-x3-24-gb-enthusiast-grafikkarte'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3090',
url: 'https://www.computeruniverse.net/de/inno3d-geforce-rtx3090-ichill-x4-24-gb-enthusiast-grafikkarte'
},
{
brand: 'kfa2',
model: 'sg oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/kfa-geforce-rtx3090-sg-oc-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3090-gaming-x-trio-24-gb-enthusiast-grafikkarte'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/msi-geforce-rtx3090-ventus-3x-oc-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3090',
url: 'https://www.computeruniverse.net/de/palit-geforce-rtx3090-gamingpro-24-gb-enthusiast-grafikkarte'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3090',
url: 'https://www.computeruniverse.net/de/palit-geforce-rtx3090-gamingpro-oc-24-gb-oc-enthusiast-grafikkarte'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx3090-xlr8-gaming-epic-x-rgb-m-24-gb-enthusiast-grafikkarte'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.computeruniverse.net/de/pny-geforce-rtx3090-xlr8-gaming-epic-x-rgb-p-24-gb-enthusiast-grafikkarte'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.computeruniverse.net/de/zotac-gaming-geforce-rtx-3090-trinity-24-gb-enthusiast-grafikkarte'
}
],
name: 'computeruniverse'
};
+123
View File
@@ -0,0 +1,123 @@
import {Store} from './store';
export const Coolblue: Store = {
labels: {
inStock: {
container: '.product-order',
text: ['bestel snel', 'morgen in huis']
},
maxPrice: {
container: '.js-order-block .sales-price__current',
euroFormat: true
},
outOfStock: {
container: '.product-order',
text: ['binnenkort leverbaar', 'tijdelijk uitverkocht']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.coolblue.nl/product/826844/'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.coolblue.nl/product/868737/'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.coolblue.nl/product/868741/'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.coolblue.nl/product/868726/'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.coolblue.nl/product/868736/'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.coolblue.nl/product/868740/'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.coolblue.nl/product/868733/'
},
{
brand: 'asus',
model: 'rog strix oc gaming',
series: '3080',
url: 'https://www.coolblue.nl/product/868731/'
},
{
brand: 'asus',
model: 'rog strix gaming',
series: '3080',
url: 'https://www.coolblue.nl/product/868732/'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.coolblue.nl/product/868738/'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3090',
url: 'https://www.coolblue.nl/product/868739/'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.coolblue.nl/product/868730/'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.coolblue.nl/product/868727/'
},
{
brand: 'asus',
model: 'rog strix oc gaming',
series: '3090',
url: 'https://www.coolblue.nl/product/868728/'
},
{
brand: 'asus',
model: 'rog strix gaming',
series: '3090',
url: 'https://www.coolblue.nl/product/868729/'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.coolblue.nl/product/868734/'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3090',
url: 'https://www.coolblue.nl/product/868735/'
}
],
name: 'coolblue'
};
+168
View File
@@ -0,0 +1,168 @@
import {Store} from './store';
export const Coolmod: Store = {
labels: {
inStock: {
container: '.product-availability',
text: ['Envío']
},
outOfStock: {
container: '.product-availability',
text: ['Sin Stock']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.coolmod.com/kfa2-geforce-rtx-2060-super-1-click-oc-8gb-gddr6-tarjeta-grafica-precio'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.coolmod.com/gigabyte-geforce-rtx-3080-eagle-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.coolmod.com/msi-geforce-rtx-3080-ventus-3x-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.coolmod.com/asus-geforce-tuf-rtx-3080-gaming-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.coolmod.com/gigabyte-geforce-rtx-3080-gaming-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.coolmod.com/msi-geforce-rtx-3080-gaming-x-trio-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.coolmod.com/asus-geforce-tuf-rtx-3080-oc-gaming-10gb-gddr6x-tarjeta-grfica-precio'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.coolmod.com/asus-rog-strix-geforce-rtx-3080-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.coolmod.com/evga-geforce-rtx-3080-xc3-black-gaming-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.coolmod.com/zotac-gaming-geforce-rtx-3080-trinity-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.coolmod.com/evga-geforce-rtx-3080-xc3-ultra-gaming-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.coolmod.com/asus-rog-strix-geforce-rtx-3080-10gb-gddr6x-tarjeta-grfica-precio'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.coolmod.com/zotac-gaming-geforce-rtx-3080-trinity-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.coolmod.com/gigabyte-geforce-rtx-3080-vision-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://www.coolmod.com/evga-geforce-rtx-3080-xc3-gaming-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.coolmod.com/evga-geforce-rtx-3080-ftw3-ultra-gaming-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.coolmod.com/gigabyte-aorus-geforce-rtx-3080-master-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3080',
url: 'https://www.coolmod.com/gigabyte-aorus-geforce-rtx-3080-xtreme-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'evga',
model: 'ftw3',
series: '3080',
url: 'https://www.coolmod.com/evga-geforce-rtx-3080-ftw3-gaming-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.coolmod.com/pny-geforce-rtx-3080-10gb-xlr8-gaming-epic-x-rgb-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'pny',
model: 'xlr8',
series: '3080',
url: 'https://www.coolmod.com/pny-geforce-rtx-3080-xlr8-gaming-epic-x-rgb-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'kfa2',
model: 'sg-oc',
series: '3080',
url: 'https://www.coolmod.com/kfa2-geforce-rtx-3080-sg-1-click-oc-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.coolmod.com/inno3d-geforce-rtx-3080-ichill-x4-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.coolmod.com/inno3d-geforce-rtx-3080-ichill-x3-10gb-gddr6x-tarjeta-grafica-precio'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.coolmod.com/inno3d-geforce-rtx-3080-twin-x2-oc-10gb-gddr6x-tarjeta-grafica-precio'
}
],
name: 'coolmod'
};
+47
View File
@@ -0,0 +1,47 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Currys: Store = {
labels: {
inStock: {
container: '#product-actions button',
text: ['add to basket']
},
maxPrice: {
container: '#product-actions span[class*="ProductPriceBlock__Price"]',
euroFormat: false // Note: Currys uses non-euroFromat as price seperator
},
outOfStock: {
container: '#product-actions .unavailable',
text: ['not available for delivery']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.currys.co.uk/gbuk/computing-accessories/components-upgrades/graphics-cards/msi-geforce-rtx-2060-8-gb-super-ventus-gp-oc-graphics-card-10196803-pdt.html'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '.resultList .product',
sitePrefix: 'https://www.currys.co.uk',
titleSelector: '.productTitle',
urlSelector: 'a[href]'
}),
urls: [
{
series: '3080',
url: 'https://www.currys.co.uk/gbuk/rtx-3080/components-upgrades/graphics-cards/324_3091_30343_xx_ba00013562-bv00313767/xx-criteria.html'
},
{
series: '3090',
url: 'https://www.currys.co.uk/gbuk/rtx-3090/components-upgrades/graphics-cards/324_3091_30343_xx_ba00013562-bv00313725/xx-criteria.html'
}
]
},
name: 'currys',
waitUntil: 'domcontentloaded'
};
+99
View File
@@ -0,0 +1,99 @@
import {Store} from './store';
export const Cyberport: Store = {
labels: {
inStock: {
container: '.tooltipAvailabilityParent',
text: ['sofort verfügbar']
},
maxPrice: {
container: '#productDetailOverview .price',
euroFormat: true
},
outOfStock: {
container: '.tooltipAvailabilityParent',
text: ['noch nicht verfügbar']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.cyberport.de?DEEP=2E12-3KL'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E07-51S'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E07-51T'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E07-51L'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E07-51N'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E21-537'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E21-52Y'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.cyberport.de?DEEP=2e21-532'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E21-52Z'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E12-3L6'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E12-3L7c'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E13-1H4'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.cyberport.de?DEEP=2E13-1H7'
}
],
name: 'cyberport'
};
+51
View File
@@ -0,0 +1,51 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Ebuyer: Store = {
labels: {
inStock: {
container: '.purchase-info',
text: ['add to basket', 'in stock']
},
maxPrice: {
container: '.purchase-info__price .price',
euroFormat: false // Note: ebuyer uses non-euroFromat as price seperator
},
outOfStock: {
container: '.purchase-info',
text: ['coming soon', 'we are expecting this item on']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.ebuyer.com/874209-gigabyte-geforce-rtx-2060-windforce-6gb-oc-graphics-card-gv-n2060wf2oc-6gd-v2'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '#list-view .listing-product',
sitePrefix: 'https://www.ebuyer.com',
titleSelector: '.listing-product-title',
urlSelector: 'a[href]'
}),
urls: [
{
series: '3070',
url: 'https://www.ebuyer.com/store/Components/cat/Graphics-Cards-Nvidia/subcat/GeForce-RTX-3070'
},
{
series: '3080',
url: 'https://www.ebuyer.com/store/Components/cat/Graphics-Cards-Nvidia/subcat/GeForce-RTX-3080'
},
{
series: '3090',
url: 'https://www.ebuyer.com/store/Components/cat/Graphics-Cards-Nvidia/subcat/GeForce-RTX-3090'
}
]
},
name: 'ebuyer',
waitUntil: 'domcontentloaded'
};
+42 -6
View File
@@ -8,29 +8,65 @@ export const EvgaEu: Store = {
}
},
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: 'ftw3 ultra',
series: '3080',
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3897-KR'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3883-KR'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3881-KR'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://eu.evga.com/products/product.aspx?pn=10G-P5-3885-KR'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://eu.evga.com/products/product.aspx?pn=24G-P5-3985-KR'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://eu.evga.com/products/product.aspx?pn=24G-P5-3987-KR'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://eu.evga.com/products/product.aspx?pn=24G-P5-3973-KR'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3090',
url: 'https://eu.evga.com/products/product.aspx?pn=24G-P5-3971-KR'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://eu.evga.com/products/product.aspx?pn=24G-P5-3975-KR'
}
],
name: 'evga-eu'
+48 -1
View File
@@ -14,6 +14,24 @@ export const Evga: Store = {
series: 'test:series',
url: 'https://www.evga.com/products/product.aspx?pn=06G-P4-2065-KR'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3070',
url: 'https://www.evga.com/products/product.aspx?pn=08G-P5-3751-KR'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3070',
url: 'https://www.evga.com/products/product.aspx?pn=08G-P5-3755-KR'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3070',
url: 'https://www.evga.com/products/product.aspx?pn=08G-P5-3767-KR'
},
{
brand: 'evga',
model: 'xc3 black',
@@ -43,8 +61,37 @@ export const Evga: Store = {
model: 'xc3 ultra',
series: '3080',
url: 'https://www.evga.com/products/product.aspx?pn=10G-P5-3885-KR'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3090',
url: 'https://www.evga.com/products/product.aspx?pn=24G-P5-3971-KR'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.evga.com/products/product.aspx?pn=24G-P5-3987-KR'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.evga.com/products/product.aspx?pn=24G-P5-3985-KR'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://www.evga.com/products/product.aspx?pn=24G-P5-3973-KR'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://www.evga.com/products/product.aspx?pn=24G-P5-3975-KR'
}
],
name: 'evga'
};
+34
View File
@@ -0,0 +1,34 @@
import {Store} from './store';
export const Gamestop: Store = {
labels: {
inStock: {
container: '.add-to-cart',
text: ['add to cart']
},
maxPrice: {
container: '.primary-details-row .actual-price',
euroFormat: false
},
outOfStock: {
container: '.add-to-cart',
text: ['not available']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.gamestop.com/nav-pc-hardware-desktops/products/clx-set-tgmsetgxe9600bm-gaming-desktop/11096665'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.gamestop.com/video-games/pc/components/graphics-cards/products/tuf-gaming-geforce-rtx-3080-graphics-card/11109446.html'
}
],
name: 'gamestop',
successStatusCodes: [[0, 399], 404]
};
+52
View File
@@ -0,0 +1,52 @@
import {Link, Store} from '..';
import {Print, logger} from '../../../logger';
import {delay, isStatusCodeInRange} from '../../../util';
import {config} from '../../../config';
type Backoff = {
count: number;
time: number;
};
const stores: Record<string, Backoff> = {};
export async function processBackoffDelay(store: Store, link: Link, statusCode: number): Promise<number> {
/**
* We treat statusCode 0 as successful as some of the puppeteer plugins
* cause side-effects resulting in an empty response object even though
* the page renders fine and its content is accessible.
*/
let backoffStatusCodes = store.backoffStatusCodes;
if (!backoffStatusCodes) {
backoffStatusCodes = [403];
}
const isBackoff = isStatusCodeInRange(statusCode, backoffStatusCodes);
let backoff = stores[store.name];
if (!backoff) {
backoff = {count: 0, time: config.browser.minBackoff};
stores[store.name] = backoff;
}
if (!isBackoff) {
if (backoff.count > 0) {
backoff.count--;
backoff.time = Math.max(backoff.time / 2, config.browser.minBackoff);
}
return -1;
}
const backoffTime = backoff.time;
logger.debug(Print.backoff(link, store, {delay: backoffTime, statusCode}, true));
await delay(backoff.time);
backoff.count++;
backoff.time = Math.min(backoff.time * 2, config.browser.maxBackoff);
return backoffTime;
}
+130
View File
@@ -0,0 +1,130 @@
import {Link, Series} from '../store';
import {logger} from '../../../logger';
export interface Card {
brand: string;
model: string;
}
interface LinksBuilderOptions {
productsSelector: string;
sitePrefix: string;
titleAttribute?: string;
titleSelector: string;
urlSelector?: string;
}
const isPartialUrlRegExp = /^\/[^/]/i;
export function getProductLinksBuilder(options: LinksBuilderOptions) {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
return (docElement: cheerio.Cheerio, series: Series): Link[] => {
const productElements = docElement.find(options.productsSelector);
const links: Link[] = [];
for (let i = 0; i < productElements.length; i++) {
const productElement = productElements.eq(i);
const titleElement = productElement.find(options.titleSelector).first();
let title: string;
if (options.titleAttribute) {
title = titleElement.attr()?.[options.titleAttribute];
} else {
title = titleElement.text()?.replace(/\n/g, ' ').trim();
}
if (!title) {
continue;
}
let urlElement = titleElement;
if (options.urlSelector) {
urlElement = urlElement.find(options.urlSelector).first();
}
let url = urlElement.attr()?.href;
if (!url) {
continue;
}
if (isPartialUrlRegExp.exec(url)) {
url = options.sitePrefix + url;
}
const card = parseCard(title);
if (card) {
links.push({
brand: card.brand as any,
model: card.model,
series,
url
});
} else {
logger.error(`Failed to parse card: ${title}`);
}
}
return links;
};
/* eslint-enable unicorn/no-fn-reference-in-iterator */
}
export function parseCard(name: string): Card | null {
name = name.replace(/\w+-\w+-[^ ]+/g, '');
name = name.replace(/\([^(]*\)/g, '');
name = name.replace(/, .+$/, '');
name = name.replace(/ with .+$/, '');
// Account for incorrect titles, e.g. MSIGeforce
name = name.replace(/geforce/i, '');
name = name.replace(/[^\w ]+/g, '');
name = name.replace(/\bgraphics card\b/gi, '');
name = name.replace(/\b(?<!founders) edition\b/gi, '');
name = name.replace(/\b(series )?bundle\b/gi, '');
name = name.replace(/\b\w+ fan\b/gi, '');
name = name.replace(/\s{2,}/g, ' ').trim();
let model = name.split(' ');
const brand = model.shift();
if (!brand) {
return null;
}
// Split non spaced TitleCase words only after extracting brand
model = model.join(' ').replace(/([A-Z][a-z]+)([A-Z][a-z]+)/g, '$1 $2').split(' ');
// Some vendors have oc at the beginning of the product name,
// store whether the card contains the term "oc" and remove
// it during filtering, then add it to the end of the name.
let isOC = false;
/* eslint-disable @typescript-eslint/prefer-regexp-exec */
model = model.filter(word => {
if (word.toLowerCase() === 'oc') {
isOC = true;
return false;
}
return !word.match(/^(nvidia|geforce|ge|force|rtx|amp[ae]re|graphics|card|gpu|pci-?e(xpress)?|ray-?tracing|ray|tracing|core|boost|epicx)$/i) &&
!word.match(/^(\d+(?:gb?|mhz)?|gb|mhz|g?ddr(\d+x?)?)$/i) &&
!word.match(/^(display ?port|hdmi|vga)$/i);
});
/* eslint-enable @typescript-eslint/prefer-regexp-exec */
if (isOC) {
model.push('OC');
}
if (model.length === 0) {
return null;
}
return {
brand: brand.toLowerCase(),
model: model.join(' ').toLowerCase().replace(/ gaming\b/g, '').trim()
};
}
+174
View File
@@ -0,0 +1,174 @@
import {NvidiaRegionInfo, regionInfos} from '../nvidia-api';
import {usingPage, usingResponse} from '../../../util';
import {Browser} from 'puppeteer';
import {config} from '../../../config';
import {logger} from '../../../logger';
import open from 'open';
interface NvidiaSessionTokenJSON {
session_token: string;
}
interface NvidiaAddToCardJSON {
location: string;
}
export class NvidiaCart {
protected readonly browser: Browser;
protected isKeepAlive = false;
protected sessionToken: string | null = null;
public constructor(browser: Browser) {
this.browser = browser;
}
public keepAlive() {
if (this.isKeepAlive) {
return;
}
const callback = async () => {
if (!this.isKeepAlive) {
return;
}
await this.refreshSessionToken();
setTimeout(callback, config.nvidia.sessionTtl);
};
this.isKeepAlive = true;
void callback();
}
public get fallbackCartUrl(): string {
return `https://www.nvidia.com/${this.regionInfo.siteLocale}/geforce/`;
}
public get regionInfo(): NvidiaRegionInfo {
const country = config.store.country;
const regionInfo = regionInfos.get(country);
if (!regionInfo) {
throw new Error(`Unknown country ${country}`);
}
return regionInfo;
}
public get sessionUrl(): string {
return `https://store.nvidia.com/store/nvidia/SessionToken?format=json&locale=${this.regionInfo.drLocale}`;
}
public async addToCard(productId: number, name: string): Promise<string> {
let cartUrl: string | undefined;
logger.info(`🚀🚀🚀 [nvidia] ${name}, starting auto add to cart 🚀🚀🚀`);
try {
logger.info(`🚀🚀🚀 [nvidia] ${name}, adding to cart 🚀🚀🚀`);
let lastError: Error | string | undefined;
/* eslint-disable no-await-in-loop */
for (let i = 0; i < config.nvidia.addToCardAttempts; i++) {
try {
cartUrl = await this.addToCartAndGetLocationRedirect(productId);
break;
} catch (error) {
logger.error(`✖ [nvidia] ${name} could not automatically add to cart, attempt ${i + 1} of ${config.nvidia.addToCardAttempts}`, error);
logger.debug(error);
lastError = error;
}
}
/* eslint-enable no-await-in-loop */
if (!cartUrl) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw lastError;
}
logger.info(`🚀🚀🚀 [nvidia] ${name}, opening checkout page 🚀🚀🚀`);
logger.info(cartUrl);
await open(cartUrl);
} catch (error) {
logger.error(`✖ [nvidia] ${name} could not automatically add to cart, opening page`);
logger.debug(error);
cartUrl = this.fallbackCartUrl;
await open(cartUrl);
}
return cartUrl;
}
public async getSessionToken(): Promise<string> {
if (!this.sessionToken) {
await this.refreshSessionToken();
}
if (!this.sessionToken) {
throw new Error('Failed to create the session_token');
}
return this.sessionToken;
}
public async refreshSessionToken(): Promise<void> {
logger.debug(' [nvidia] refreshing session token');
try {
const result = await usingResponse(this.browser, this.sessionUrl, async response => {
return response?.json() as NvidiaSessionTokenJSON | undefined;
});
if (typeof result !== 'object' || result === null || !('session_token' in result)) {
throw new Error('malformed response');
}
this.sessionToken = result.session_token;
logger.debug(` [nvidia] session_token=${result.session_token}`);
} catch (error) {
const message: string = typeof error === 'object' ? error.message : error;
logger.error(`✖ [nvidia] ${message}`);
}
}
protected async addToCartAndGetLocationRedirect(productId: number): Promise<string> {
const url = 'https://api-prod.nvidia.com/direct-sales-shop/DR/add-to-cart';
const sessionToken = await this.getSessionToken();
logger.info(` [nvidia] session_token=${sessionToken}`);
const locationData = await usingPage(this.browser, async page => {
page.removeAllListeners('request');
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
void interceptedRequest.continue({
headers: {
...interceptedRequest.headers(),
'content-type': 'application/json',
nvidia_shop_id: sessionToken
},
method: 'POST',
postData: JSON.stringify({
products: [
{productId, quantity: 1}
]
})
});
});
const response = await page.goto(url, {waitUntil: 'networkidle0'});
if (response === null) {
throw new Error('NvidiaAddToCartUnavailable');
}
return response.json() as Promise<NvidiaAddToCardJSON>;
});
return locationData.location;
}
}
+31 -115
View File
@@ -1,135 +1,51 @@
import {Browser, Response} from 'puppeteer';
import {NvidiaRegionInfo, regionInfos} from '../nvidia-api';
import {Config} from '../../../config';
import {Browser} from 'puppeteer';
import {Link} from '../store';
import {Logger} from '../../../logger';
import open from 'open';
import {NvidiaCart} from './nvidia-cart';
import {config} from '../../../config';
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';
let country = config.store.country;
if (!regionInfos.has(country)) {
country = 'usa';
}
const defaultRegionInfo: NvidiaRegionInfo = {drLocale: 'en_us', fe2060SuperId: 5379432500, fe3080Id: 5438481700, fe3090Id: null, nvidiaLocale: 'en_us'};
return regionInfos.get(country) ?? defaultRegionInfo;
const regionInfo = regionInfos.get(country);
if (!regionInfo) {
throw new Error(`LogicException could not retrieve region info for ${country}`);
}
return regionInfo;
}
function digitalRiverStockUrl(id: number, drLocale: string): string {
return `https://api.digitalriver.com/v1/shoppers/me/products/${id}/inventory-status?` +
`&apiKey=${nvidiaApiKey}` +
`&locale=${drLocale}` +
timestampUrlParameter();
function nvidiaStockUrl(id: number, drLocale: string, currency: string): string {
return `https://api-prod.nvidia.com/direct-sales-shop/DR/products/${drLocale}/${currency}/${id}?` +
timestampUrlParameter().slice(1);
}
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()}`;
}
let cart: NvidiaCart;
export function generateSetupAction() {
return async (browser: Browser) => {
const {drLocale, nvidiaLocale} = getRegionInfo();
cart = new NvidiaCart(browser);
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);
if (config.browser.open) {
cart.keepAlive();
}
await page.close();
};
}
export function generateOpenCartAction(id: number, nvidiaLocale: string, drLocale: string, cardName: string) {
return async (browser: Browser) => {
const page = await browser.newPage();
export function generateOpenCartAction(id: number, cardName: string) {
return async () => {
const url = await cart.addToCard(id, cardName);
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;
return url;
};
}
export function generateLinks(): Link[] {
const {drLocale, nvidiaLocale, fe3080Id, fe3090Id, fe2060SuperId} = getRegionInfo();
const {drLocale, fe3080Id, fe3090Id, fe2060SuperId, currency} = getRegionInfo();
const links: Link[] = [];
@@ -137,9 +53,9 @@ export function generateLinks(): Link[] {
links.push({
brand: 'test:brand',
model: 'test:model',
openCartAction: generateOpenCartAction(fe2060SuperId, nvidiaLocale, drLocale, 'TEST CARD debug'),
openCartAction: generateOpenCartAction(fe2060SuperId, 'TEST CARD debug'),
series: 'test:series',
url: digitalRiverStockUrl(fe2060SuperId, drLocale)
url: nvidiaStockUrl(fe2060SuperId, drLocale, currency)
});
}
@@ -147,9 +63,9 @@ export function generateLinks(): Link[] {
links.push({
brand: 'nvidia',
model: 'founders edition',
openCartAction: generateOpenCartAction(fe3080Id, nvidiaLocale, drLocale, 'nvidia founders edition 3080'),
openCartAction: generateOpenCartAction(fe3080Id, 'nvidia founders edition 3080'),
series: '3080',
url: digitalRiverStockUrl(fe3080Id, drLocale)
url: nvidiaStockUrl(fe3080Id, drLocale, currency)
});
}
@@ -157,9 +73,9 @@ export function generateLinks(): Link[] {
links.push({
brand: 'nvidia',
model: 'founders edition',
openCartAction: generateOpenCartAction(fe3090Id, nvidiaLocale, drLocale, 'nvidia founders edition 3090'),
openCartAction: generateOpenCartAction(fe3090Id, 'nvidia founders edition 3090'),
series: '3090',
url: digitalRiverStockUrl(fe3090Id, drLocale)
url: nvidiaStockUrl(fe3090Id, drLocale, currency)
});
}
+135 -18
View File
@@ -1,69 +1,186 @@
import {config, defaultStoreData} from '../../config';
import {Adorama} from './adorama';
import {Alternate} from './alternate';
import {AlternateNL} from './alternate-nl';
import {Amazon} from './amazon';
import {AmazonCa} from './amazon-ca';
import {AmazonDe} from './amazon-de';
import {AmazonEs} from './amazon-es';
import {AmazonNl} from './amazon-nl';
import {AmazonUk} from './amazon-uk';
import {Aria} from './aria';
import {Asus} from './asus';
import {AsusDe} from './asus-de';
import {Azerty} from './azerty';
import {BAndH} from './bandh';
import {BestBuy} from './bestbuy';
import {BestBuyCa} from './bestbuy-ca';
import {Config} from '../../config';
import {Box} from './box';
import {Caseking} from './caseking';
import {Ccl} from './ccl';
import {Computeruniverse} from './computeruniverse';
import {Coolblue} from './coolblue';
import {Coolmod} from './coolmod';
import {Currys} from './currys';
import {Cyberport} from './cyberport';
import {Ebuyer} from './ebuyer';
import {Evga} from './evga';
import {EvgaEu} from './evga-eu';
import {Logger} from '../../logger';
import {Gamestop} from './gamestop';
import {Mediamarkt} from './mediamarkt';
import {MicroCenter} from './microcenter';
import {Mindfactory} from './mindfactory';
import {Newegg} from './newegg';
import {NeweggCa} from './newegg-ca';
import {Notebooksbilliger} from './notebooksbilliger';
import {Novatech} from './novatech';
import {Nvidia} from './nvidia';
import {NvidiaApi} from './nvidia-api';
import {OfficeDepot} from './officedepot';
import {Overclockers} from './overclockers';
import {PCComponentes} from './pccomponentes';
import {Pny} from './pny';
import {ProshopDE} from './proshop-de';
import {ProshopDK} from './proshop-dk';
import {Saturn} from './saturn';
import {Scan} from './scan';
import {Store} from './store';
import {Very} from './very';
import {Zotac} from './zotac';
import {logger} from '../../logger';
const masterList = new Map([
export const storeList = new Map([
[Adorama.name, Adorama],
[Alternate.name, Alternate],
[AlternateNL.name, AlternateNL],
[Amazon.name, Amazon],
[AmazonCa.name, AmazonCa],
[AmazonDe.name, AmazonDe],
[AmazonEs.name, AmazonEs],
[AmazonNl.name, AmazonNl],
[AmazonUk.name, AmazonUk],
[Aria.name, Aria],
[Asus.name, Asus],
[AsusDe.name, AsusDe],
[Azerty.name, Azerty],
[BAndH.name, BAndH],
[BestBuy.name, BestBuy],
[BestBuyCa.name, BestBuyCa],
[Box.name, Box],
[Caseking.name, Caseking],
[Ccl.name, Ccl],
[Computeruniverse.name, Computeruniverse],
[Coolblue.name, Coolblue],
[Coolmod.name, Coolmod],
[Currys.name, Currys],
[Cyberport.name, Cyberport],
[Ebuyer.name, Ebuyer],
[Evga.name, Evga],
[EvgaEu.name, EvgaEu],
[Gamestop.name, Gamestop],
[Mediamarkt.name, Mediamarkt],
[MicroCenter.name, MicroCenter],
[Mindfactory.name, Mindfactory],
[Newegg.name, Newegg],
[NeweggCa.name, NeweggCa],
[Notebooksbilliger.name, Notebooksbilliger],
[Novatech.name, Novatech],
[Nvidia.name, Nvidia],
[NvidiaApi.name, NvidiaApi],
[OfficeDepot.name, OfficeDepot],
[Overclockers.name, Overclockers],
[PCComponentes.name, PCComponentes],
[Pny.name, Pny],
[ProshopDE.name, ProshopDE],
[ProshopDK.name, ProshopDK],
[Saturn.name, Saturn],
[Scan.name, Scan],
[Very.name, Very],
[Zotac.name, Zotac]
]);
const list = new Map();
const brands = new Set();
const models = new Set();
const series = new Set();
const stores = new Map();
for (const name of Config.store.stores) {
if (masterList.has(name)) {
list.set(name, masterList.get(name));
} else {
const logString = `No store named ${name}, skipping.`;
Logger.warn(logString);
function filterBrandsSeriesModels(stores: Map<string, Store>) {
brands.clear();
series.clear();
models.clear();
for (const store of stores.values()) {
for (const link of store.links) {
brands.add(link.brand);
series.add(link.series);
models.add(link.model);
}
if (store.minPageSleep === undefined) {
store.minPageSleep = defaultStoreData.minPageSleep;
}
if (store.maxPageSleep === undefined) {
store.maxPageSleep = defaultStoreData.maxPageSleep;
}
}
}
Logger.info(` selected stores: ${Array.from(list.keys()).join(', ')}`);
function printConfig() {
if (config.store.stores.length > 0) {
logger.info(` selected stores: ${config.store.stores.map(store => store.name).join(', ')}`);
}
if (Config.store.showOnlyBrands.length > 0) {
Logger.info(` selected brands: ${Config.store.showOnlyBrands.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.map(entry => {
return entry.series ? entry.name + ' (' + entry.series + ')' : entry.name;
}).join(', ')}`);
}
if (config.store.showOnlySeries.length > 0) {
logger.info(` selected series: ${config.store.showOnlySeries.join(', ')}`);
}
}
if (Config.store.showOnlyModels.length > 0) {
Logger.info(` selected models: ${Config.store.showOnlyModels.join(', ')}`);
export function updateStores() {
stores.clear();
for (const storeData of config.store.stores) {
const store = storeList.get(storeData.name);
if (store) {
stores.set(storeData.name, store);
store.minPageSleep = storeData.minPageSleep;
store.maxPageSleep = storeData.maxPageSleep;
} else {
logger.warn(`No store named ${storeData.name}, skipping.`);
}
}
filterBrandsSeriesModels(stores);
printConfig();
}
if (Config.store.showOnlySeries.length > 0) {
Logger.info(` selected series: ${Config.store.showOnlySeries.join(', ')}`);
updateStores();
export function getAllBrands() {
return Array.from(brands);
}
export const Stores = Array.from(list.values()) as Store[];
export function getAllSeries() {
return Array.from(series);
}
export function getAllModels() {
return Array.from(models);
}
export function getStores() {
return stores;
}
export * from './store';
+161
View File
@@ -0,0 +1,161 @@
import {Store} from './store';
export const Mediamarkt: Store = {
labels: {
maxPrice: {
container: 'span[font-family="price"]',
euroFormat: false
},
outOfStock: {
container: '#root',
text: ['dieser artikel ist aktuell nicht verfügbar.']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.mediamarkt.de/de/product/-2592355.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3070',
url: 'https://www.mediamarkt.de/de/product/-2691244.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3070',
url: 'https://www.mediamarkt.de/de/product/-2691439.html'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3070',
url: 'https://www.mediamarkt.de/de/product/-2695942.html'
},
{
brand: 'zotac',
model: 'twin edge',
series: '3070',
url: 'https://www.mediamarkt.de/de/product/-2691365.html'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2681869.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2681871.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2681859.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2681861.html'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2683942.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2683937.html'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2684241.html'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2684238.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2683227.html'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2683229.html'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.mediamarkt.de/de/product/-2683243.html'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2681863.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2681866.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2681855.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2681857.html'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2691441.html'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2691440.html'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2684235.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.mediamarkt.de/de/product/-2683226.html'
}
],
name: 'mediamarkt'
};
+70 -53
View File
@@ -1,7 +1,9 @@
import {Config} from '../../config';
import {Store} from './store';
import {Link, Store} from './store';
import {config} from '../../config';
import {logger} from '../../logger';
const microCenterLocations = config.store.microCenterLocation;
const MicroCenterLocation = Config.store.microCenterLocation;
const microCenterLocationToId: Map<string, string> = new Map([
['web', '029'],
['brooklyn', '115'],
@@ -10,14 +12,14 @@ const microCenterLocationToId: Map<string, string> = new Map([
['chicago', '151'],
['columbus', '141'],
['dallas', '131'],
['devin', '181'],
['denver', '181'],
['duluth', '065'],
['fairfax', '081'],
['flushing', '145'],
['houston', '155'],
['madison-heights', '055'],
['marietta', '041'],
['mayfiend-heights', '051'],
['mayfield-heights', '051'],
['north-jersey', '075'],
['overland-park', '191'],
['parkville', '125'],
@@ -31,11 +33,65 @@ const microCenterLocationToId: Map<string, string> = new Map([
['yonkers', '105']
]);
let links: Link[] = [];
let storeId: string;
if (microCenterLocationToId.get(MicroCenterLocation) === undefined) {
storeId = '029';
} else {
storeId = microCenterLocationToId.get(MicroCenterLocation)!;
for (const microCenterLocation of microCenterLocations) {
if (microCenterLocationToId.get(microCenterLocation) === undefined) {
const logString = `No MicroCenter location named ${microCenterLocation}, skipping.`;
logger.warn(logString);
} else {
storeId = microCenterLocationToId.get(microCenterLocation)!;
links = links.concat([
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: `https://www.microcenter.com/product/618433/evga-geforce-rtx-2060-ko-ultra-overclocked-dual-fan-6gb-gddr6-pcie-30-graphics-card/?storeid=${storeId}`
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: `https://www.microcenter.com/product/628344/evga-geforce-rtx-3080-xc3-ultra-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: `https://www.microcenter.com/product/628346/evga-geforce-rtx-3080-ftw3-ultra-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3080',
url: `https://www.microcenter.com/product/628331/msi-geforce-rtx-3080-ventus-3x-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: `https://www.microcenter.com/product/628303/asus-geforce-rtx-3080-tuf-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: `https://www.microcenter.com/product/628330/msi-geforce-rtx-3080-gaming-x-trio-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: `https://www.microcenter.com/product/628340/evga-geforce-rtx-3080-xc3-black-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: `https://www.microcenter.com/product/628607/zotac-geforce-rtx-3080-trinity-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
}
]);
}
}
export const MicroCenter: Store = {
@@ -43,51 +99,12 @@ export const MicroCenter: Store = {
inStock: {
container: '#cart-options',
text: ['in stock']
},
maxPrice: {
container: 'span[id="pricing"]',
euroFormat: false
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: `https://www.microcenter.com/product/618433/evga-geforce-rtx-2060-ko-ultra-overclocked-dual-fan-6gb-gddr6-pcie-30-graphics-card/?storeid=${storeId}`
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: `https://www.microcenter.com/product/628344/evga-geforce-rtx-3080-xc3-ultra-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3080',
url: `https://www.microcenter.com/product/628331/msi-geforce-rtx-3080-ventus-3x-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: `https://www.microcenter.com/product/628303/asus-geforce-rtx-3080-tuf-gaming-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: `https://www.microcenter.com/product/628330/msi-geforce-rtx-3080-gaming-x-trio-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: `https://www.microcenter.com/product/628340/evga-geforce-rtx-3080-xc3-black-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: `https://www.microcenter.com/product/628607/zotac-geforce-rtx-3080-trinity-overclocked-triple-fan-10gb-gddr6x-pcie-40-graphics-card/?storeid=${storeId}`
}
],
links,
name: 'microcenter'
};
+87
View File
@@ -0,0 +1,87 @@
import {Store} from './store';
export const Mindfactory: Store = {
labels: {
inStock: {
container: '.pshipping',
text: ['lagernd', 'verfügbar']
},
maxPrice: {
container: 'div[class="pprice"]',
euroFormat: true
},
outOfStock: {
container: '.pshipping',
text: ['ohne liefertermin']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.mindfactory.de/product_info.php/8GB-MSI-GeForce-RTX2070Super-GAMING-X-DDR6--Retail-_1329683.html'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-Gigabyte-RTX3080-AORUS-MASTER-GDDR6X-3xHDMI-3xDP--Retail-_1378681.html'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-Gigabyte-RTX3080-AORUS-XTREME-GDDR6X-3xHDMI-3xDP--Retail-_1380484.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-Gigabyte-GeForce-RTX-3080-Gaming-OC-3xDP-2x-HDMI--Retail-_1376263.html'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-Gigabyte-RTX3080-VISION-OC-GDDR6X-2xHDMI-3xDP--Retail-_1378682.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-MSI-GeForce-RTX-3080-Gaming-X-TRIO--Retail-_1376481.html'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-Palit-GeForce-RTX-3080-GamingPro--Retail-_1376483.html'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.mindfactory.de/product_info.php/10GB-Zotac-GeForce-RTX-3080-TRINITY-OC--GDDR6X-_1377143.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3090',
url: 'https://www.mindfactory.de/product_info.php/24GB-Gigabyte-GeForce-3090-Gaming-OC--Retail-_1377265.html'
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3090',
url: 'https://www.mindfactory.de/product_info.php/24GB-MSI-GeForce-RTX-3090-VENTUS-3X-DDR6--Retail-_1377475.html'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3090',
url: 'https://www.mindfactory.de/product_info.php/24GB-Palit-GeForce-RTX-3090-GamingPro-DDR6--Retail-_1377233.html'
}
],
name: 'mindfactory'
};
+161 -1
View File
@@ -7,8 +7,12 @@ export const NeweggCa: Store = {
text: ['are you a human?']
},
inStock: {
container: '#landingpage-cart .btn-primary span',
container: 'div#ProductBuy .btn-primary',
text: ['add to cart']
},
maxPrice: {
container: 'div#app div.product-price > ul > li.price-current > strong',
euroFormat: false
}
},
links: [
@@ -78,6 +82,12 @@ export const NeweggCa: Store = {
series: '3080',
url: 'https://www.newegg.ca/asus-geforce-rtx-3080-tuf-rtx3080-o10g-gaming/p/N82E16814126452'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.newegg.ca/asus-geforce-rtx-3080-rog-strix-rtx3080-o10g-gaming/p/N82E16814126457'
},
{
brand: 'msi',
model: 'gaming x trio',
@@ -113,6 +123,156 @@ export const NeweggCa: Store = {
model: 'rog strix',
series: '3090',
url: 'https://www.newegg.ca/asus-geforce-rtx-3090-rog-strix-rtx3090-o24g-gaming/p/N82E16814126456'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3090',
url: 'https://www.newegg.ca/evga-geforce-rtx-3090-24g-p5-3975-kr/p/N82E16814487524'
},
{
brand: 'evga',
model: 'xc3',
series: '3090',
url: 'https://www.newegg.ca/evga-geforce-rtx-3090-24g-p5-3973-kr/p/N82E16814487523'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.newegg.ca/evga-geforce-rtx-3090-24g-p5-3987-kr/p/N82E16814487526'
},
{
brand: 'evga',
model: 'ftw3',
series: '3090',
url: 'https://www.newegg.ca/evga-geforce-rtx-3090-24g-p5-3985-kr/p/N82E16814487525'
},
{
brand: 'msi',
model: 'ventus 2x',
series: '3070',
url: 'https://www.newegg.ca/msi-geforce-rtx-3070-rtx-3070-ventus-2x-oc/p/N82E16814137602'
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3070',
url: 'https://www.newegg.ca/msi-geforce-rtx-3070-rtx-3070-ventus-3x-oc/p/N82E16814137601'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3070',
url: 'https://www.newegg.ca/msi-geforce-rtx-3070-rtx-3070-gaming-x-trio/p/N82E16814137603'
},
{
brand: 'asus',
model: 'dual',
series: '3070',
url: 'https://www.newegg.ca/asus-geforce-rtx-3070-dual-rtx3070-8g/p/N82E16814126460'
},
{
brand: 'asus',
model: 'dual oc',
series: '3070',
url: 'https://www.newegg.ca/asus-geforce-rtx-3070-dual-rtx3070-o8g/p/N82E16814126459'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3070',
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3070-gv-n3070gaming-oc-8gd/p/N82E16814932342'
},
{
brand: 'asus',
model: 'rog strix',
series: '3070',
url: 'https://www.newegg.ca/asus-geforce-rtx-3070-rog-strix-rtx3070-o8g-gaming/p/N82E16814126458'
},
{
brand: 'zotac',
model: 'twin edge',
series: '3070',
url: 'https://www.newegg.ca/zotac-geforce-rtx-3070-zt-a30700e-10p/p/N82E16814500501'
},
{
brand: 'gigabyte',
model: 'eagle',
series: '3070',
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3070-gv-n3070eagle-8gd/p/N82E16814932344'
},
{
brand: 'evga',
model: 'xc3 black gaming',
series: '3070',
url: 'https://www.newegg.ca/evga-geforce-rtx-3070-08g-p5-3751-kr/p/N82E16814487528'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3070',
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3070-gv-n3070vision-oc-8gd/p/N82E16814932360'
},
{
brand: 'zotac',
model: 'twin edge oc',
series: '3070',
url: 'https://www.newegg.ca/zotac-geforce-rtx-3070-zt-a30700h-10p/p/N82E16814500505'
},
{
brand: 'pny',
model: 'uprising',
series: '3070',
url: 'https://www.newegg.ca/pny-geforce-rtx-3070-vcg30708dfmpb/p/N82E16814133812'
},
{
brand: 'asus',
model: 'tuf',
series: '3070',
url: 'https://www.newegg.ca/asus-geforce-rtx-3070-tuf-rtx3070-o8g-gaming/p/N82E16814126461'
},
{
brand: 'evga',
model: 'ftw3 gaming',
series: '3070',
url: 'https://www.newegg.ca/evga-geforce-rtx-3070-08g-p5-3765-kr/p/N82E16814487531'
},
{
brand: 'gigabyte',
model: 'aorus',
series: '3070',
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3070-gv-n3070aorus-m-8gd/p/N82E16814932359'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3070',
url: 'https://www.newegg.ca/gigabyte-geforce-rtx-3070-gv-n3070eagle-oc-8gd/p/N82E16814932343'
},
{
brand: 'evga',
model: 'xc3 ultra gaming',
series: '3070',
url: 'https://www.newegg.ca/evga-geforce-rtx-3070-08g-p5-3755-kr/p/N82E16814487530'
},
{
brand: 'evga',
model: 'ftw3 ultra gaming',
series: '3070',
url: 'https://www.newegg.ca/evga-geforce-rtx-3070-08g-p5-3767-kr/p/N82E16814487532'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3070',
url: 'https://www.newegg.ca/pny-geforce-rtx-3070-vcg30708tfxppb/p/N82E16814133811'
},
{
brand: 'evga',
model: 'xc3 gaming',
series: '3070',
url: 'https://www.newegg.ca/evga-geforce-rtx-3070-08g-p5-3753-kr/p/N82E16814487529'
}
],
name: 'newegg-ca'
+58 -5
View File
@@ -7,8 +7,12 @@ export const Newegg: Store = {
text: ['are you a human?']
},
inStock: {
container: '#landingpage-cart .btn-primary span',
container: 'div#ProductBuy .btn-primary',
text: ['add to cart']
},
maxPrice: {
container: 'div#app div.product-price > ul > li.price-current > strong',
euroFormat: false
}
},
links: [
@@ -25,6 +29,20 @@ export const Newegg: Store = {
series: '3080',
url: 'https://www.newegg.com/asus-geforce-rtx-3080-tuf-rtx3080-10g-gaming/p/N82E16814126453'
},
{
brand: 'evga',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487518',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3897-kr/p/N82E16814487518'
},
{
brand: 'evga',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487519',
model: 'ftw3',
series: '3080',
url: 'https://www.newegg.com/evga-geforce-rtx-3080-10g-p5-3895-kr/p/N82E16814487519'
},
{
brand: 'evga',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487522',
@@ -82,6 +100,13 @@ export const Newegg: Store = {
series: '3080',
url: 'https://www.newegg.com/gigabyte-geforce-rtx-3080-gv-n3080eagle-oc-10gd/p/N82E16814932330'
},
{
brand: 'gigabyte',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814932336',
model: 'aorus master',
series: '3080',
url: 'https://www.newegg.com/gigabyte-geforce-rtx-3080-gv-n3080aorus-m-10gd/p/N82E16814932336'
},
{
brand: 'zotac',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814500502',
@@ -110,13 +135,27 @@ export const Newegg: Store = {
series: '3080',
url: 'https://www.newegg.com/zotac-geforce-rtx-3080-zt-t30800j-10p/p/N82E16814500504'
},
{
brand: 'pny',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814133809',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.newegg.com/pny-geforce-rtx-3080-vcg308010tfxppb/p/N82E16814133809'
},
{
brand: 'asus',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814126455',
model: 'tuf',
series: '3090',
url: 'https://www.newegg.com/asus-geforce-rtx-3090-tuf-rtx3090-24g-gaming/p/N82E16814126455'
},
{
brand: 'asus',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814126456',
model: 'rog strix',
series: '3090',
url: 'https://www.newegg.com/asus-geforce-rtx-3090-rog-strix-rtx3090-o24g-gaming/p/N82E16814126456'
},
{
brand: 'msi',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814137595',
@@ -148,24 +187,38 @@ export const Newegg: Store = {
{
brand: 'evga',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487525',
model: 'ftw3 gaming',
model: 'ftw3',
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',
model: 'xc3 ultra',
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',
model: 'ftw3 ultra',
series: '3090',
url: 'https://www.newegg.com/evga-geforce-rtx-3090-24g-p5-3987-kr/p/N82E16814487526'
},
{
brand: 'evga',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487527',
model: 'xc3 black',
series: '3090',
url: 'https://www.newegg.com/evga-geforce-rtx-3090-24g-p5-3971-kr/p/N82E16814487527'
},
{
brand: 'evga',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814487523',
model: 'xc3',
series: '3090',
url: 'https://www.newegg.com/evga-geforce-rtx-3090-24g-p5-3973-kr/p/N82E16814487523'
},
{
brand: 'gigabyte',
cartUrl: 'https://secure.newegg.com/Shopping/AddtoCart.aspx?Submit=ADD&ItemList=N82E16814932327',
+149
View File
@@ -0,0 +1,149 @@
import {Store} from './store';
export const Notebooksbilliger: Store = {
labels: {
inStock: {
container: '.warehouse',
text: ['sofort ab lager', 'verfügbarkeit: ca. 2-4 werktage', 'verfügbarkeit: ca. 5-10 werktage']
},
maxPrice: {
container: 'form[name="cart_quantity"] span[class*="product-price__regular"]',
euroFormat: true
},
outOfStock: [{
container: '.warehouse',
text: ['liefertermin noch unbestimmt']
},
{
container: '.soldOut',
text: ['dieses produkt ist leider ausverkauft.']
},
{
container: '#product_error_text',
text: ['leider ist dieser artikel nicht mehr verfügbar.']
}]
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.notebooksbilliger.de/gainward+geforce+rtx+2070+super+phoenix+v1+grafikkarte+656238'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3070',
url: 'https://www.notebooksbilliger.de/inno3d+geforce+rtx+3070+ichill+x3+grafikkarte+684162'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3070',
url: 'https://www.notebooksbilliger.de/nvidia+geforce+rtx+3070+founders+edition+685357'
},
{
brand: 'gainward',
model: 'phoenix gs',
series: '3080',
url: 'https://www.notebooksbilliger.de/gainward+geforce+rtx+3080+phoenix+gs+10gb+gddr6x+grafikkarte+677618'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.notebooksbilliger.de/gigabyte+geforce+rtx+3080+eagle+oc+10gb+gddr6x+grafikkarte+677501'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.notebooksbilliger.de/gigabyte+geforce+rtx+3080+gaming+oc+10gb+gddr6x+grafikkarte+677499'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.notebooksbilliger.de/inno3d+geforce+rtx+3080+ichill+x3+grafikkarte+678588'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.notebooksbilliger.de/inno3d+geforce+rtx+3080+twin+x2+oc+grafikkarte+679190'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.notebooksbilliger.de/msi+geforce+rtx+3080+gaming+x+trio+10g+grafikkarte+678527'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3080',
url: 'https://www.notebooksbilliger.de/nvidia+geforce+rtx+3080+founders+edition+683301'
},
{
brand: 'palit',
model: 'gaming pro',
series: '3080',
url: 'https://www.notebooksbilliger.de/palit+geforce+rtx+3080+gamingpro+10gb+gddr6x+grafikkarte+677609'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3080',
url: 'https://www.notebooksbilliger.de/palit+geforce+rtx+3080+gamingpro+oc+10gb+gddr6x+grafikkarte+677606'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.notebooksbilliger.de/pny+geforce+rtx+3080+xlr8+gaming+10gb+gddr6x+grafikkarte+677407'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.notebooksbilliger.de/pny+geforce+rtx+3080+10gb+xlr8+gaming+revel+epic+x+grafikkarte+677412'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.notebooksbilliger.de/asus+rog+strix+geforce+rtx+3090+24gb+gddr6x+grafikkarte+677343'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.notebooksbilliger.de/asus+rog+strix+geforce+rtx+3090+oc+24gb+gddr6x+grafikkarte+677308'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.notebooksbilliger.de/gigabyte+geforce+rtx+3090+master+24gb+gddr6x+grafikkarte+683868'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3090',
url: 'https://www.notebooksbilliger.de/nvidia+geforce+rtx+3090+founders+edition+683300'
},
{
brand: 'palit',
model: 'gaming pro oc',
series: '3090',
url: 'https://www.notebooksbilliger.de/palit+geforce+rtx+3090+gamingpro+oc+24gb+gddr6x+grafikkarte+677599'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://www.notebooksbilliger.de/zotac+gaming+geforce+rtx+3090+trinity+24gb+gddr6x+grafikkarte+677550'
}
],
name: 'notebooksbilliger'
};
+52
View File
@@ -0,0 +1,52 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Novatech: Store = {
labels: {
inStock: {
container: '.newspec-specprice',
text: ['add to basket']
},
maxPrice: {
container: 'p[class="newspec-price"]',
euroFormat: false // Note: Novatech uses non-euroFromat as price seperator
},
outOfStock: {
container: '.newspec-pricesection',
text: [
'very short supply, no confirmed date',
'this product is only available to buy when in stock',
'ordered upon request',
'price to be confirmed'
]
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.novatech.co.uk/products/gigabyte-geforce-rtx-2060-oc-v2-6g-graphics-card/gv-n2060oc-6gdv2.html'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '.seo-container .search-box-results',
sitePrefix: 'https://www.novatech.co.uk',
titleSelector: '.search-box-title',
urlSelector: 'a[href]'
}),
urls: [
{
series: '3080',
url: 'https://www.novatech.co.uk/products/components/nvidiageforcegraphicscards/nvidiartxseries/nvidiartx3080/?i=200'
},
{
series: '3090',
url: 'https://www.novatech.co.uk/products/components/nvidiageforcegraphicscards/nvidiartxseries/nvidiartx3090/?i=200'
}
]
},
name: 'novatech',
waitUntil: 'domcontentloaded'
};
+23 -20
View File
@@ -4,33 +4,36 @@ import {Store} from './store';
// Region/country set by config file, silently ignores null / missing values and defaults to usa
export interface NvidiaRegionInfo {
currency: string;
drLocale: string;
fe3080Id: number | null;
fe3090Id: number | null;
fe2060SuperId: number | null;
nvidiaLocale: string;
siteLocale: 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'}]
['austria', {currency: 'EUR', drLocale: 'de_de', fe2060SuperId: 5394902900, fe3080Id: 5440853700, fe3090Id: 5444941400, siteLocale: 'de-at'}],
['belgium', {currency: 'EUR', drLocale: 'fr_fr', fe2060SuperId: 5394902700, fe3080Id: 5438795700, fe3090Id: 5438795600, siteLocale: 'fr-be'}],
['canada', {currency: 'CAD', drLocale: 'en_us', fe2060SuperId: 5379432500, fe3080Id: 5438481700, fe3090Id: 5438481600, siteLocale: 'en-us'}],
['czechia', {currency: 'CZK', drLocale: 'en_gb', fe2060SuperId: 5394902800, fe3080Id: 5438793800, fe3090Id: 5438793600, siteLocale: 'cs-cz'}],
['denmark', {currency: 'DKK', drLocale: 'en_gb', fe2060SuperId: 5394903100, fe3080Id: 5438793300, fe3090Id: null, siteLocale: 'da-dk'}],
['finland', {currency: 'EUR', drLocale: 'en_gb', fe2060SuperId: 5394903100, fe3080Id: 5438793300, fe3090Id: null, siteLocale: 'fi-fi'}],
['france', {currency: 'EUR', drLocale: 'fr_fr', fe2060SuperId: 5394903200, fe3080Id: 5438795200, fe3090Id: 5438761500, siteLocale: 'fr-fr'}],
['germany', {currency: 'EUR', drLocale: 'de_de', fe2060SuperId: 5394902900, fe3080Id: 5438792300, fe3090Id: 5438761400, siteLocale: 'de-de'}],
['great_britain', {currency: 'GBP', drLocale: 'en_gb', fe2060SuperId: 5394903300, fe3080Id: 5438792800, fe3090Id: 5438792700, siteLocale: 'en-gb'}],
['ireland', {currency: 'GBP', drLocale: 'en_gb', fe2060SuperId: 5394903300, fe3080Id: 5438792800, fe3090Id: 5438792700, siteLocale: 'en-gb'}],
['italy', {currency: 'EUR', drLocale: 'it_it', fe2060SuperId: 5394903400, fe3080Id: 5438796200, fe3090Id: 5438796100, siteLocale: 'it-it'}],
['luxembourg', {currency: 'EUR', drLocale: 'fr_fr', fe2060SuperId: 5394902700, fe3080Id: 5438795700, fe3090Id: 5438795600, siteLocale: 'fr-be'}],
['netherlands', {currency: 'EUR', drLocale: 'nl_nl', fe2060SuperId: 5394903500, fe3080Id: 5438796700, fe3090Id: 5438796600, siteLocale: 'nl-nl'}],
['norway', {currency: 'NOK', drLocale: 'no_no', fe2060SuperId: 5394903600, fe3080Id: 5438797200, fe3090Id: 5438797100, siteLocale: 'nb-no'}],
['poland', {currency: 'PLN', drLocale: 'pl_pl', fe2060SuperId: 5394903700, fe3080Id: 5438797700, fe3090Id: 5438797600, siteLocale: 'pl-pl'}],
['portugal', {currency: 'EUR', drLocale: 'en_gb', fe2060SuperId: null, fe3080Id: 5438794300, fe3090Id: null, siteLocale: 'en-gb'}],
['russia', {currency: 'RUB', drLocale: 'ru_ru', fe2060SuperId: null, fe3080Id: null, fe3090Id: null, siteLocale: 'ru-ru'}],
['spain', {currency: 'EUR', drLocale: 'es_es', fe2060SuperId: 5394903000, fe3080Id: 5438794800, fe3090Id: 5438794700, siteLocale: 'es-es'}],
['sweden', {currency: 'SEK', drLocale: 'sv_se', fe2060SuperId: 5394903900, fe3080Id: 5438798100, fe3090Id: 5438761600, siteLocale: 'sv-se'}],
// https://github.com/jef/nvidia-snatcher/issues/407 This fe2080SuperID is for the Shield TV which is out of stock in the US
['usa', {currency: 'USD', drLocale: 'en_us', fe2060SuperId: 5355772500, fe3080Id: 5438481700, fe3090Id: 5438481600, siteLocale: 'en-us'}]
]);
export const NvidiaApi: Store = {
+43 -7
View File
@@ -6,29 +6,65 @@ export const Nvidia: Store = {
container: 'body',
text: ['are you a human?']
},
inStock: {
container: '.main-container',
text: ['add to cart']
}
inStock: [
{
container: '.main-container',
text: ['add to cart']
},
{
container: '.inner',
text: ['add to cart']
}
]
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.newegg.com/evga-geforce-rtx-2060-06g-p4-2066-kr/p/N82E16814487488'
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/'
},
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.nvidia.com/en-us/geforce/graphics-cards/rtx-2060-super/'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3080',
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/?page=1&limit=9&locale=en-us&gpu=RTX%203080&category=GPU&manufacturer=NVIDIA&gpu_filter=RTX%203090~1,RTX%203080~1,RTX%203070~0,TITAN%20RTX~0,RTX%202080%20Ti~0,RTX%202070%20SUPER~0,RTX%202060%20SUPER~0,RTX%202060~0,GTX%201660%20Ti~0,GTX%201660%20SUPER~0,GTX%201660~0,GTX%201650%20SUPER~0,GTX%201650~0'
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/?page=1&limit=9&locale=en-us&category=GPU&gpu=RTX%203080'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3080',
url: 'https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3080'
},
{
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'
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/?page=1&limit=9&locale=en-us&category=GPU&gpu=RTX%203090'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3090',
url: 'https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3090'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3070',
url: 'https://www.nvidia.com/en-us/shop/geforce/gpu/?page=1&limit=9&locale=en-us&category=GPU&gpu=RTX%203070'
},
{
brand: 'nvidia',
model: 'founders edition',
series: '3070',
url: 'https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3070'
}
],
name: 'nvidia'
+4
View File
@@ -9,6 +9,10 @@ export const OfficeDepot: Store = {
inStock: {
container: '#productPurchase',
text: ['add to cart']
},
maxPrice: {
container: 'span[class^="price_column right"]',
euroFormat: false
}
},
links: [
+55
View File
@@ -0,0 +1,55 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Overclockers: Store = {
labels: {
inStock: {
container: '#detailbox',
text: ['add to basket', 'in stock']
},
maxPrice: {
container: 'div[class="article_details_price"]',
euroFormat: false // Note: Overclockers uses non-euroFromat as price seperator
},
outOfStock: {
container: '#detailbox',
text: ['out of stock', 'pre order', 'bought to order']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.overclockers.co.uk/gigabyte-geforce-rtx-2060-oc-rev2-6144mb-gddr6-pci-express-graphics-card-gx-1bj-gi.html'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: '.ck_listing .artbox',
sitePrefix: 'https://www.overclockers.co.uk',
titleAttribute: 'data-description',
titleSelector: 'a[href].producttitles'
}),
urls: [
{
series: '3070',
url: 'https://www.overclockers.co.uk/pc-components/graphics-cards/nvidia/geforce-rtx-3070'
},
{
series: '3080',
// Need to add support to detect pagination so this can be dynamically detected
url: [
'https://www.overclockers.co.uk/pc-components/graphics-cards/nvidia/geforce-rtx-3080',
'https://www.overclockers.co.uk/pc-components/graphics-cards/nvidia/geforce-rtx-3080?p=2'
]
},
{
series: '3090',
url: 'https://www.overclockers.co.uk/pc-components/graphics-cards/nvidia/geforce-rtx-3090'
}
]
},
name: 'overclockers',
waitUntil: 'domcontentloaded'
};
+148
View File
@@ -0,0 +1,148 @@
import {Store} from './store';
export const PCComponentes: Store = {
labels: {
inStock: {
container: '#btnsWishAddBuy',
text: ['Comprar']
},
maxPrice: {
container: '#precio-main',
euroFormat: true
},
outOfStock: {
container: '#btnsWishAddBuy',
text: ['Avísame']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.pccomponentes.com/gigabyte-geforce-gtx-1660-super-oc-6gb-gddr6'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.pccomponentes.com/gigabyte-geforce-rtx-3080-eagle-oc-10g-10gb-gddr6x'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.pccomponentes.com/msi-geforce-rtx-3080-ventus-3x-oc-10gb-gddr6x'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.pccomponentes.com/asus-tuf-geforce-rtx-3080-10gb-gddr6x'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.pccomponentes.com/gigabyte-geforce-rtx-3080-gaming-oc-10g-10gb-gddr6x'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.pccomponentes.com/msi-geforce-rtx-3080-gaming-x-trio-10gb-gddr6x'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.pccomponentes.com/asus-tuf-geforce-rtx-3080-oc-10gb-gddr6x'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.pccomponentes.com/asus-rog-strix-geforce-rtx-3080-10g-gaming-oc-10gb-gddr6x'
},
{
brand: 'evga',
model: 'xc3 black',
series: '3080',
url: 'https://www.pccomponentes.com/evga-geforce-rtx-3080-xc3-black-gaming-10gb-gddr6x'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.pccomponentes.com/zotac-gaming-geforce-rtx-3080-trinity-10gb-gddr6x'
},
{
brand: 'evga',
model: 'xc3 ultra',
series: '3080',
url: 'https://www.pccomponentes.com/evga-geforce-rtx-3080-xc3-ultra-gaming-10gb-gddr6x'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.pccomponentes.com/asus-rog-strix-geforce-rtx-3080-gaming-10gb-gddr6x'
},
{
brand: 'zotac',
model: 'trinity oc',
series: '3080',
url: 'https://www.pccomponentes.com/zotac-gaming-geforce-rtx-3080-trinity-oc-10gb-gddr6x'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.pccomponentes.com/gigabyte-geforce-rtx-3080-vision-oc-10gb-gddr6x'
},
{
brand: 'evga',
model: 'xc3',
series: '3080',
url: 'https://www.pccomponentes.com/evga-geforce-rtx-3080-xc3-gaming-10gb-gddr6x'
},
{
brand: 'evga',
model: 'ftw3 ultra',
series: '3080',
url: 'https://www.pccomponentes.com/evga-geforce-rtx-3080-ftw3-ultra-gaming-10gb-gddr6x'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.pccomponentes.com/gigabyte-aorus-geforce-rtx-3080-master-10gb-gddr6x'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3080',
url: 'https://www.pccomponentes.com/gigabyte-aorus-geforce-rtx-3080-xtreme-10gb-gddr6x'
},
{
brand: 'evga',
model: 'ftw3',
series: '3080',
url: 'https://www.pccomponentes.com/evga-geforce-rtx-3080-ftw3-gaming-10gb-gddr6x'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.pccomponentes.com/pny-geforce-rtx-3080-xlr8-gaming-epic-x-rgb-10gb-gddr6x'
},
{
brand: 'pny',
model: 'xlr8',
series: '3080',
url: 'https://www.pccomponentes.com/pny-geforce-rtx-3080-epic-x-rgb-triple-fan-xlr8-gaming-edition-10gb-gddr6x'
}
],
name: 'pccomponentes'
};
+61
View File
@@ -0,0 +1,61 @@
import {Store} from './store';
export const Pny: Store = {
labels: {
inStock: {
container: '#ctl01_lbtnAddToCart',
text: ['add to cart']
},
maxPrice: {
container: 'span[itemprop="price"]',
euroFormat: false
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.pny.com/pny-geforce-gtx-1660-super-gaming-oc-sf'
},
{
brand: 'pny',
model: 'dual fan',
series: '3070',
url: 'https://www.pny.com/pny-geforce-rtx-3070-8gb-df'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3070',
url: 'https://www.pny.com/geforce-rtx-3070-xlr8-gaming-epic-x-rgb-triple-fan'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.pny.com/geforce-rtx-3080-xlr8-gaming-epic-x-rgb-triple-fan-m'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3080',
url: 'https://www.pny.com/geforce-rtx-3080-xlr8-gaming-epic-x-rgb-triple-fan-p'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.pny.com/geforce-rtx-3090-xlr8-gaming-epic-x-rgb-triple-fan-m'
},
{
brand: 'pny',
model: 'xlr8 rgb',
series: '3090',
url: 'https://www.pny.com/geforce-rtx-3090-xlr8-gaming-epic-x-rgb-triple-fan-p'
}
],
name: 'pny'
};
+156
View File
@@ -0,0 +1,156 @@
import {Store} from './store';
export const ProshopDE: Store = {
labels: {
maxPrice: {
container: '.site-currency-wrapper > span[class="site-currency-attention"]',
euroFormat: true
},
outOfStock: {
container: '.site-currency-attention',
text: ['die ware ist leider nicht mehr verfügbar.']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.proshop.de/2797958'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.proshop.de/2876857'
},
{
brand: 'asus',
model: ' rog strix oc',
series: '3080',
url: 'https://www.proshop.de/2876859'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.proshop.de/2876763'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.proshop.de/2876861'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.proshop.de/2876835'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.proshop.de/2876837'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.proshop.de/2876838'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.proshop.de/2878401'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.proshop.de/2878969'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.proshop.de/2878971'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.proshop.de/2878968'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.proshop.de/2876877'
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3080',
url: 'https://www.proshop.de/2876878'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.proshop.de/2876879'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.proshop.de/2876865'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.proshop.de/2876867'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.proshop.de/2876764'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.proshop.de/2876869'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.proshop.de/2876840'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3090',
url: 'https://www.proshop.de/2876839'
},
{
brand: 'gigabyte',
model: 'turbo',
series: '3090',
url: 'https://www.proshop.de/2878410'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.proshop.de/2876881'
}
],
name: 'proshop-de'
};
+154
View File
@@ -0,0 +1,154 @@
import {Store} from './store';
export const ProshopDK: Store = {
labels: {
inStock: {
container: '.site-stock',
text: ['1-2 dages levering', 'fjernlager']
},
maxPrice: {
container: '.site-currency-wrapper > span[class="site-currency-attention"]',
euroFormat: true
},
outOfStock: {
container: '.site-stock',
text: ['bestilt']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-2060-TUF-OC-6GB-GDDR6-RAM-Grafikkort/2694767'
},
{
brand: 'asus',
model: 'tuf gaming',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3080-TUF-10GB-GDDR6X-RAM-Grafikkort/2876763'
},
{
brand: 'asus',
model: 'tuf gaming oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3080-TUF-OC-10GB-GDDR6X-RAM-Grafikkort/2876861'
},
{
brand: 'asus',
model: 'strix gaming',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3080-ROG-STRIX-10GB-GDDR6X-RAM-Grafikkort/2876857'
},
{
brand: 'asus',
model: 'strix gaming oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3080-ROG-STRIX-OC-10GB-GDDR6X-RAM-Grafikkort/2876859'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/MSI-GeForce-RTX-3080-GAMING-X-TRIO-10GB-GDDR6X-RAM-Grafikkort/2876877'
},
{
brand: 'msi',
model: 'ventus 3x',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/MSI-GeForce-RTX-3080-Ventus-3X-10GB-GDDR6X-RAM-Grafikkort/2876878'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/MSI-GeForce-RTX-3080-Ventus-3X-OC-10GB-GDDR6X-RAM-Grafikkort/2876879'
},
{
brand: 'inno3d',
model: 'ichill 4x',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/Inno3D-GeForce-RTX-3080-iCHILL-X4-10GB-GDDR6X-SDRAM-Grafikkort/2878971'
},
{
brand: 'inno3d',
model: 'ichill 3x',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/Inno3D-GeForce-RTX-3080-iCHILL-X3-10GB-GDDR6X-SDRAM-Grafikkort/2878969'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/GIGABYTE-GeForce-RTX-3080-GAMING-OC-10GB-GDDR6X-RAM-Grafikkort/2876838'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/GIGABYTE-GeForce-RTX-3080-Eagle-OC-10GB-GDDR6X-RAM-Grafikkort/2876837'
},
{
brand: 'inno3d',
model: 'twin x2 oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/Inno3D-GeForce-RTX-3080-Twin-X2-OC-10GB-GDDR6X-SDRAM-Grafikkort/2878968'
},
{
brand: 'gigabyte',
model: 'vision oc',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/GIGABYTE-GeForce-RTX-3080-Vision-OC-10GB-GDDR6X-RAM-Grafikkort/2878401'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3080',
url: 'https://www.proshop.dk/Grafikkort/GIGABYTE-GeForce-RTX-3080-AORUS-Master-10GB-GDDR6X-RAM-Grafikkort/2876835'
},
{
brand: 'asus',
model: 'tuf gaming',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3090-TUF-24GB-GDDR6X-RAM-Grafikkort/2876764'
},
{
brand: 'asus',
model: 'tuf gaming oc',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3090-TUF-OC-24GB-GDDR6X-RAM-Grafikkort/2876869'
},
{
brand: 'asus',
model: 'strix gaming',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3090-ROG-STRIX-24GB-GDDR6X-RAM-Grafikkort/2876865'
},
{
brand: 'asus',
model: 'strix gaming oc',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/ASUS-GeForce-RTX-3090-ROG-STRIX-OC-24GB-GDDR6X-RAM-Grafikkort/2876867'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/MSI-GeForce-RTX-3090-GAMING-X-TRIO-24GB-GDDR6X-RAM-Grafikkort/2876881'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/GIGABYTE-GeForce-RTX-3090-AORUS-Master-24GB-GDDR6X-RAM-Grafikkort/2876840'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3090',
url: 'https://www.proshop.dk/Grafikkort/GIGABYTE-GeForce-RTX-3090-AORUS-Xtreme-24GB-GDDR6X-RAM-Grafikkort/2876839'
}
],
name: 'proshop-dk'
};
+161
View File
@@ -0,0 +1,161 @@
import {Store} from './store';
export const Saturn: Store = {
labels: {
maxPrice: {
container: 'span[font-family="price"]',
euroFormat: false // Note: Saturn uses non-euroFromat as price seperator
},
outOfStock: {
container: '#root',
text: ['dieser artikel ist aktuell nicht verfügbar.']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.saturn.de/de/product/-2592355.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3070',
url: 'https://www.saturn.de/de/product/-2691244.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3070',
url: 'https://www.saturn.de/de/product/-2691439.html'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3070',
url: 'https://www.saturn.de/de/product/-2695942.html'
},
{
brand: 'zotac',
model: 'twin edge',
series: '3070',
url: 'https://www.saturn.de/de/product/-2691365.html'
},
{
brand: 'asus',
model: 'rog strix',
series: '3080',
url: 'https://www.saturn.de/de/product/-2681869.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3080',
url: 'https://www.saturn.de/de/product/-2681871.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3080',
url: 'https://www.saturn.de/de/product/-2681859.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3080',
url: 'https://www.saturn.de/de/product/-2681861.html'
},
{
brand: 'gigabyte',
model: 'eagle oc',
series: '3080',
url: 'https://www.saturn.de/de/product/-2683942.html'
},
{
brand: 'gigabyte',
model: 'gaming oc',
series: '3080',
url: 'https://www.saturn.de/de/product/-2683937.html'
},
{
brand: 'inno3d',
model: 'ichill x3',
series: '3080',
url: 'https://www.saturn.de/de/product/-2684241.html'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3080',
url: 'https://www.saturn.de/de/product/-2684238.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3080',
url: 'https://www.saturn.de/de/product/-2683227.html'
},
{
brand: 'msi',
model: 'ventus 3x oc',
series: '3080',
url: 'https://www.saturn.de/de/product/-2683229.html'
},
{
brand: 'zotac',
model: 'trinity',
series: '3080',
url: 'https://www.saturn.de/de/product/-2683243.html'
},
{
brand: 'asus',
model: 'rog strix',
series: '3090',
url: 'https://www.saturn.de/de/product/-2681863.html'
},
{
brand: 'asus',
model: 'rog strix oc',
series: '3090',
url: 'https://www.saturn.de/de/product/-2681866.html'
},
{
brand: 'asus',
model: 'tuf',
series: '3090',
url: 'https://www.saturn.de/de/product/-2681855.html'
},
{
brand: 'asus',
model: 'tuf oc',
series: '3090',
url: 'https://www.saturn.de/de/product/-2681857.html'
},
{
brand: 'gigabyte',
model: 'aorus master',
series: '3090',
url: 'https://www.saturn.de/de/product/-2691441.html'
},
{
brand: 'gigabyte',
model: 'aorus xtreme',
series: '3090',
url: 'https://www.saturn.de/de/product/-2691440.html'
},
{
brand: 'inno3d',
model: 'ichill x4',
series: '3090',
url: 'https://www.saturn.de/de/product/-2684235.html'
},
{
brand: 'msi',
model: 'gaming x trio',
series: '3090',
url: 'https://www.saturn.de/de/product/-2683226.html'
}
],
name: 'saturn'
};
+57
View File
@@ -0,0 +1,57 @@
import {Store} from './store';
import {getProductLinksBuilder} from './helpers/card';
export const Scan: Store = {
disableAdBlocker: true,
labels: {
captcha: [{
container: '#challenge-form',
text: ['hcaptcha_submit']
}],
inStock: {
container: '.buyPanel .priceAvailability',
text: ['add to basket', 'in stock']
},
maxPrice: {
container: '.buyPanel .price',
euroFormat: false // Note: Scan uses non-euroFromat as price seperator
},
outOfStock: {
container: '.buyPanel .priceAvailability',
text: ['pre order']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.scan.co.uk/products/msi-geforce-rtx-2060-ventus-xs-oc-6gb-gddr6-vr-ready-graphics-card-1920-core-1710mhz-boost'
}
],
linksBuilder: {
builder: getProductLinksBuilder({
productsSelector: 'ul.productColumns li.product',
sitePrefix: 'https://www.scan.co.uk',
titleSelector: '.details .description',
urlSelector: 'a[href]'
}),
ttl: 300000,
urls: [
{
series: '3070',
url: 'https://www.scan.co.uk/shop/computer-hardware/gpu-nvidia/nvidia-geforce-rtx-3070-graphics-cards'
},
{
series: '3080',
url: 'https://www.scan.co.uk/shop/computer-hardware/gpu-nvidia/nvidia-geforce-rtx-3080-graphics-cards'
},
{
series: '3090',
url: 'https://www.scan.co.uk/shop/computer-hardware/gpu-nvidia/nvidia-geforce-rtx-3090-graphics-cards'
}
]
},
name: 'scan',
waitUntil: 'domcontentloaded'
};
+41 -5
View File
@@ -1,13 +1,20 @@
import {Browser, LoadEvent} from 'puppeteer';
export type Element = {
container: string;
container?: string;
text: string[];
};
export type Pricing = {
container: string;
euroFormat?: boolean;
};
export type Series = 'test:series' | '3070' | '3080' | '3090';
export type Link = {
brand: 'test:brand' | 'asus' | 'evga' | 'gigabyte' | 'pny' | 'msi' | 'nvidia' | 'zotac';
series: 'test:series' | '3070' | '3080' | '3090';
brand: 'test:brand' | 'asus' | 'evga' | 'gainward' | 'gigabyte' | 'inno3d' | 'kfa2' | 'msi' | 'nvidia' | 'palit' | 'pny' | 'zotac';
series: Series;
model: string;
url: string;
cartUrl?: string;
@@ -15,15 +22,44 @@ export type Link = {
screenshot?: string;
};
export type LabelQuery = Element[] | Element | string[];
export type Labels = {
captcha?: Element;
inStock: Element;
bannedSeller?: LabelQuery;
captcha?: LabelQuery;
container?: string;
inStock?: LabelQuery;
outOfStock?: LabelQuery;
maxPrice?: Pricing;
};
export type StatusCodeRangeArray = Array<(number | [number, number])>;
export type Store = {
/**
* The range of status codes which will trigger backoff, i.e. an increasing
* delay between requests. Setting an empty array will disable the feature.
* If not defined, the default range will be used: 403.
*/
backoffStatusCodes?: StatusCodeRangeArray;
disableAdBlocker?: boolean;
links: Link[];
linksBuilder?: {
builder: (docElement: cheerio.Cheerio, series: Series) => Link[];
ttl?: number;
urls: Array<{series: Series; url: string | string[]}>;
};
labels: Labels;
name: string;
setupAction?: (browser: Browser) => void;
/**
* The range of status codes which considered successful, i.e. without error
* allowing request parsing to continue. Setting an empty array will cause
* all requests to fail. If not defined, the default range will be used:
* 0 -> 399 inclusive.
*/
successStatusCodes?: StatusCodeRangeArray;
waitUntil?: LoadEvent;
minPageSleep?: number;
maxPageSleep?: number;
};
+83
View File
@@ -0,0 +1,83 @@
import {Link, Store} from './store';
import {logger} from '../../logger';
import {parseCard} from './helpers/card';
export const Very: Store = {
labels: {
inStock: {
container: '.stockMessaging .indicator',
text: ['available', 'low stock']
},
maxPrice: {
container: '.priceNow',
euroFormat: false // Note: Very uses non-euroFromat as price seperator
},
outOfStock: {
container: '.stockMessaging .indicator',
text: ['pre-order']
}
},
links: [
{
brand: 'test:brand',
model: 'test:model',
series: 'test:series',
url: 'https://www.very.co.uk/msi-geforce-gtx-1660-ti-gaming-x-6g-graphics-card/1600350984.prd'
}
],
linksBuilder: {
builder: (docElement, series) => {
const productElements = docElement.find('.productList .product');
const links: Link[] = [];
for (let i = 0; i < productElements.length; i++) {
const productElement = productElements.eq(i);
const titleElement = productElement.find('.productTitle').first();
const title = titleElement.text()?.replace(/\n/g, ' ').trim();
if (!title || ['RTX', series]
.map(x => title.toLowerCase().includes(x.toLowerCase()))
.filter(x => !x).length > 0
) {
continue;
}
const url = titleElement.attr()?.href;
if (!url) {
continue;
}
const card = parseCard(title);
if (card) {
links.push({
brand: card.brand as any,
model: card.model,
series,
url
});
} else {
logger.error(`Failed to parse card: ${title}`);
}
}
return links;
},
ttl: 300000,
urls: [
{
series: '3070',
url: 'https://www.very.co.uk/electricals/pc-components/graphics-cards/e/b/118786.end?sort=newin,0&numProducts=100'
},
{
series: '3080',
url: 'https://www.very.co.uk/electricals/pc-components/graphics-cards/e/b/118786.end?sort=newin,0&numProducts=100'
},
{
series: '3090',
url: 'https://www.very.co.uk/electricals/pc-components/graphics-cards/e/b/118786.end?sort=newin,0&numProducts=100'
}
]
},
name: 'very'
};
+12 -1
View File
@@ -1,10 +1,15 @@
import {Store} from './store';
export const Zotac: Store = {
backoffStatusCodes: [403, 503],
labels: {
inStock: {
container: '.add-to-cart-wrapper',
text: ['add to cart']
},
maxPrice: {
container: 'div[class="product-shop"] span[class="price"]',
euroFormat: false
}
},
links: [
@@ -22,9 +27,15 @@ export const Zotac: Store = {
},
{
brand: 'zotac',
model: 'trinity OC',
model: 'trinity oc',
series: '3080',
url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-3080-trinity-oc-zt-a30800j-10p'
},
{
brand: 'zotac',
model: 'trinity',
series: '3090',
url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-3090-trinity-zt-a30900d-10p'
}
],
name: 'zotac'
+1
View File
@@ -0,0 +1 @@
declare module 'node-pagerduty';
+22 -1
View File
@@ -1 +1,22 @@
declare module 'play-sound';
declare module 'play-sound' {
export interface Options {
players?: string[];
player?: string;
}
export interface PlayOptions {
[key: string]: any;
}
export interface PlaySound {
player: string;
play: ((file: string, callback: (error: Error) => void) => PlayerProcess) & ((file: string, options: PlayOptions, callback: (error: Error) => void) => PlayerProcess);
}
export interface PlayerProcess {
kill: () => void;
}
export default function (options?: Options): PlaySound;
}
+25
View File
@@ -0,0 +1,25 @@
declare module 'puppeteer-extra-plugin-block-resources' {
import {PuppeteerExtraPlugin} from 'puppeteer-extra';
export type ResourceType =
'document' |
'eventsource' |
'fetch' |
'font' |
'image' |
'manifest' |
'media' |
'other' |
'script' |
'stylesheet' |
'texttrack' |
'websocket' |
'xhr';
export interface Options {
availableTypes?: Set<ResourceType>;
blockedTypes?: Set<ResourceType>;
}
export default function (options?: Options): PuppeteerExtraPlugin;
}
+59 -1
View File
@@ -1 +1,59 @@
declare module 'pushbullet';
declare module '@jef/pushbullet' {
export type DeviceParams = string | number | Record<string, any>;
export type PushBulletCallback = ((error: Error) => void) | ((error?: null, response: any) => void);
export interface ListOptions {
active?: boolean;
cursor?: string;
limit?: number;
}
export interface HistoryOptions extends ListOptions {
modified_after?: number;
}
export interface PushBulletStream {
connect: () => void;
close: () => void;
on: ((event: 'connect' | 'close' | 'nop', callback: () => void) => void) & ((event: 'error', callback: (error: any) => void) => void) & ((event: 'message', callback: (message: any) => void) => void) & ((event: 'tickle', callback: (tickle: any) => void) => void) & ((event: 'push', callback: (push: any) => void) => void);
}
export class PushBullet {
constructor(apiKey: string, options?: {fullResponses: boolean}): this;
me(callback: PushBulletCallback);
devices(options: ListOptions, callback: PushBulletCallback);
devices(callback: PushBulletCallback);
createDevice(options: Record<string, any>, callback: PushBulletCallback);
updateDevice(deviceIden: string, deviceOptions: Record<string, any>, callback: PushBulletCallback);
deleteDevice(deviceIden: string, callback: PushBulletCallback);
note(deviceParams: DeviceParams, title: string, body: string, callback: PushBulletCallback);
link(deviceParams: DeviceParams, name: string, url: string, body: string, callback: PushBulletCallback);
file(deviceParams: DeviceParams, filePath: string, message: string, callback: PushBulletCallback);
dismissPush(pushIden: DeviceParams, callback: PushBulletCallback);
deletePush(pushIden: DeviceParams, callback: PushBulletCallback);
deleteAllPushes(callback: PushBulletCallback);
history(options: HistoryOptions, callback: PushBulletCallback);
history(callback: PushBulletCallback);
subscriptions(options: ListOptions, callback: PushBulletCallback);
subscriptions(callback: PushBulletCallback);
subscribe(channelTag: string, callback: PushBulletCallback);
unsubscribe(subscriptionIden: string, callback: PushBulletCallback);
muteSubscription(subscriptionIden: string, callback: PushBulletCallback);
unmuteSubscription(subscriptionIden: string, callback: PushBulletCallback);
channelInfo(channelTag: string, callback: PushBulletCallback);
chats(options: ListOptions, callback: PushBulletCallback);
chats(callback: PushBulletCallback);
createChat(email: string, callback: PushBulletCallback);
deleteChat(chatIden: string, callback: PushBulletCallback);
muteChat(chatIden: string, callback: PushBulletCallback);
unmuteChat(chatIden: string, callback: PushBulletCallback);
sendSMS(options: Record<string, any>, callback: PushBulletCallback);
sendClipboard(options: Record<string, any>, callback: PushBulletCallback);
dismissEphemeral(options: Record<string, any>, callback: PushBulletCallback);
stream(): PushBulletStream;
enableEncryption(encryptionPassword: string, userIden: string): PushBulletStream;
}
export default PushBullet;
}
+55 -1
View File
@@ -1 +1,55 @@
declare module 'pushover-notifications';
declare module 'pushover-notifications' {
export type PushoverCallback = ((error: Error) => void) | ((error?: null, response: any) => void);
export type Sound =
'pushover' |
'bike' |
'bugle' |
'cashregister' |
'classical' |
'cosmic' |
'falling' |
'gamelan' |
'incoming' |
'intermission' |
'magic' |
'mechanical' |
'pianobar' |
'siren' |
'spacealarm' |
'tugboat' |
'alien' |
'climb' |
'persistent' |
'echo' |
'updown' |
'vibrate' |
'none';
export interface PushoverOptions {
token: string;
user: string;
httpOptions?: {proxy: string};
onerror?: (error: Error | string) => void;
update_sounds?: boolean;
}
export interface PushoverMessage {
message: string;
file?: string | {name: string; data: string};
device?: string;
title?: string;
url?: string;
url_title?: string;
priority?: number;
sound?: Sound;
timestamp?: number;
}
export class Pushover {
constructor(options: PushoverOptions);
send(message: PushoverMessage, callback: PushoverCallback);
}
export default Pushover;
}

Some files were not shown because too many files have changed in this diff Show More