From 908ed358826f9de530f5892ded1a54964a304d15 Mon Sep 17 00:00:00 2001 From: Bryan Berger Date: Tue, 22 Sep 2020 21:49:40 -0400 Subject: [PATCH 1/3] feat(notification): twitter integration (#224) --- .env-example | 5 + README.md | 5 + package-lock.json | 324 ++++++++++++++++++++++++++++--- package.json | 2 + src/config.ts | 9 +- src/notification/notification.ts | 10 + src/notification/twitter.ts | 29 +++ 7 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 src/notification/twitter.ts diff --git a/.env-example b/.env-example index 3e47e50a1a..c3f520557d 100644 --- a/.env-example +++ b/.env-example @@ -23,4 +23,9 @@ COUNTRY="usa" SCREENSHOT="true" TELEGRAM_ACCESS_TOKEN="" TELEGRAM_CHAT_ID="1234" +TWITTER_CONSUMER_KEY= +TWITTER_CONSUMER_SECRET= +TWITTER_ACCESS_TOKEN_KEY= +TWITTER_ACCESS_TOKEN_SECRET= +TWITTER_TWEET_TAGS= USER_AGENT="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" diff --git a/README.md b/README.md index f86be7b557..fe416e300d 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ Here is a list of variables that you can use to customize your newly copied `.en | `TELEGRAM_ACCESS_TOKEN` | Telegram access token | | `TELEGRAM_CHAT_ID` | Telegram chat ID | | `USER_AGENT` | Custom User-Agent header for HTTP requests | Default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36` | +| `TWITTER_CONSUMER_KEY` | Twitter Consumer Key | Generate all Twitter keys at: https://developer.twitter.com/ | +| `TWITTER_CONSUMER_SECRET` | Twitter Consumer Secret | +| `TWITTER_ACCESS_TOKEN_KEY` | Twitter Token Key | +| `TWITTER_ACCESS_TOKEN_SECRET` | Twitter Token Secret | +| `TWITTER_TWEET_TAGS` | Optional list of hashtags to append to the tweet message | Eg: "`#NVIDIA` `#NVIDIAINSTOCK`" | > :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. diff --git a/package-lock.json b/package-lock.json index 6520f91c65..c6cfd3d848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -338,6 +338,12 @@ "integrity": "sha512-deXFjLZc1h6SOh3hicVgD+S2EAkhSBGX/vdlD4nTzCjjOFQ+bfNiXocQ21xJjFAUwqaCeyvOQMgrnbg4QEV63A==", "dev": true }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, "@types/chrome": { "version": "0.0.91", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.91.tgz", @@ -483,12 +489,40 @@ "@types/node": "*" } }, + "@types/request": { + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true + }, + "@types/twitter": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@types/twitter/-/twitter-1.7.0.tgz", + "integrity": "sha512-acHFbtq5eQs60g/2XWgV2LG+KGknz31hFJeaLtsdsmeezUSO2l7d8Lm7DnSfh5sNx+/NMP3yEAdVR9MrTGKtDw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/request": "*" + } + }, "@types/url-join": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/url-join/-/url-join-4.0.0.tgz", @@ -634,7 +668,6 @@ "version": "6.12.5", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -769,6 +802,14 @@ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -816,6 +857,11 @@ } } }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -836,8 +882,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "at-least-node": { "version": "1.0.0", @@ -851,6 +896,16 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, "axios": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", @@ -947,6 +1002,14 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, "bl": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", @@ -1289,6 +1352,11 @@ "quick-lru": "^4.0.1" } }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1449,7 +1517,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -1630,6 +1697,14 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -1762,8 +1837,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "des.js": { "version": "1.0.1", @@ -1892,6 +1966,15 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "elliptic": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", @@ -2774,6 +2857,11 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -2886,11 +2974,15 @@ } } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { "version": "1.2.0", @@ -2959,8 +3051,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -3113,6 +3204,11 @@ "for-in": "^1.0.1" } }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, "form-data": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", @@ -3193,6 +3289,14 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -3318,6 +3422,20 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -3427,6 +3545,16 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -3897,8 +4025,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-unc-path": { "version": "1.0.0", @@ -3944,6 +4071,11 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3965,6 +4097,11 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -3983,11 +4120,15 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -3995,6 +4136,11 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -4014,6 +4160,17 @@ "universalify": "^1.0.0" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -4496,14 +4653,12 @@ "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { "version": "2.1.27", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, "requires": { "mime-db": "1.44.0" } @@ -4792,6 +4947,11 @@ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "obj-props": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.3.0.tgz", @@ -5172,6 +5332,11 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -5285,6 +5450,11 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -5319,8 +5489,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "pupa": { "version": "2.0.1", @@ -5472,6 +5641,11 @@ "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.2.tgz", "integrity": "sha512-+3Xcj+kiMiouZK1Ws8yGBTyl8WMPZZdELgl/iVxYqNwDdlaObBHMhEGPRC6Zb9t0BE27ikOoOqSIO1cKZOtsDA==" }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -5695,6 +5869,50 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, "reserved-words": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", @@ -5787,8 +6005,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { "version": "7.3.2", @@ -6137,6 +6354,22 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -6540,6 +6773,15 @@ "repeat-string": "^1.6.1" } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "trim-newlines": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", @@ -6602,6 +6844,35 @@ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "twitter": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/twitter/-/twitter-1.7.1.tgz", + "integrity": "sha1-B2I3jx3BwFDkj2ZqypBOJLGpYvQ=", + "requires": { + "deep-extend": "^0.5.0", + "request": "^2.72.0" + }, + "dependencies": { + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + } + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6784,7 +7055,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -6876,6 +7146,16 @@ "spdx-expression-parse": "^3.0.0" } }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", diff --git a/package.json b/package.json index 8e341f2672..7ed288250b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "puppeteer-extra-plugin-adblocker": "^2.11.6", "puppeteer-extra-plugin-stealth": "^2.6.1", "pushover-notifications": "^1.2.2", + "twitter": "^1.7.1", "winston": "^3.3.3" }, "devDependencies": { @@ -41,6 +42,7 @@ "@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", diff --git a/src/config.ts b/src/config.ts index 6dce70dd54..f1cc6c559a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -48,7 +48,14 @@ const notifications = { accessToken: process.env.TELEGRAM_ACCESS_TOKEN ?? '', chatId: process.env.TELEGRAM_CHAT_ID ?? '' }, - test: process.env.NOTIFICATION_TEST === 'true' + test: process.env.NOTIFICATION_TEST === 'true', + twitter: { + accessTokenKey: process.env.TWITTER_ACCESS_TOKEN_KEY ?? '', + accessTokenSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET ?? '', + consumerKey: process.env.TWITTER_CONSUMER_KEY ?? '', + consumerSecret: process.env.TWITTER_CONSUMER_SECRET ?? '', + tweetTags: process.env.TWITTER_TWEET_TAGS ?? '' + } }; const page = { diff --git a/src/notification/notification.ts b/src/notification/notification.ts index 9ca515b387..8fb924c181 100644 --- a/src/notification/notification.ts +++ b/src/notification/notification.ts @@ -8,6 +8,7 @@ import {sendPushoverNotification} from './pushover'; import {sendSMS} from './sms'; import {sendSlackMessage} from './slack'; import {sendTelegramMessage} from './telegram'; +import {sendTweet} from './twitter'; const notifications = Config.notifications; @@ -46,4 +47,13 @@ export function sendNotification(cartUrl: string, link: Link) { if (notifications.desktop) { sendDesktopNotification(cartUrl, link); } + + if ( + notifications.twitter.accessTokenKey && + notifications.twitter.accessTokenSecret && + notifications.twitter.consumerKey && + notifications.twitter.consumerSecret + ) { + sendTweet(cartUrl, link); + } } diff --git a/src/notification/twitter.ts b/src/notification/twitter.ts new file mode 100644 index 0000000000..a701295421 --- /dev/null +++ b/src/notification/twitter.ts @@ -0,0 +1,29 @@ +import {Config} from '../config'; +import {Link} from '../store/model'; +import {Logger} from '../logger'; +import Twitter from 'twitter'; + +const twitter = Config.notifications.twitter; + +const client = new Twitter({ + access_token_key: twitter.accessTokenKey, + access_token_secret: twitter.accessTokenSecret, + consumer_key: twitter.consumerKey, + consumer_secret: twitter.consumerSecret +}); + +export function sendTweet(cartUrl: string, link: Link) { + let status = `🛎️ Stock Notification: ${link.brand} ${link.model}\n${cartUrl}`; + + if (twitter.tweetTags) { + status += `\n\n${twitter.tweetTags}`; + } + + client.post('statuses/update', {status}, err => { + if (err) { + Logger.error(err); + } else { + Logger.info(`↗ twitter notification sent: ${cartUrl}`); + } + }); +} From a70e63bb35113d9636f9fbfc64ff8eb471939c48 Mon Sep 17 00:00:00 2001 From: Colin Farrell Date: Wed, 23 Sep 2020 03:59:10 +0200 Subject: [PATCH 2/3] chore(store): add netherland nvidia (#210) Co-authored-by: fuckingrobot Co-authored-by: Jef LeCompte --- README.md | 1 + src/store/model/nvidia.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index fe416e300d..dc8bb0b9fb 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ Here is a list of variables that you can use to customize your newly copied `.en | ireland | `✔` | | | | italy | `✔` | | | | luxembourg | `✔` | | Nvidia supports debug | +| netherlands | `✔` | | Nvidia supports debug | | poland | `✔` | | | | portugal | `✔` | | | | russia | | | Missing all IDs | diff --git a/src/store/model/nvidia.ts b/src/store/model/nvidia.ts index 854c3364d5..d27d203b27 100644 --- a/src/store/model/nvidia.ts +++ b/src/store/model/nvidia.ts @@ -24,6 +24,7 @@ export const regionInfos = new Map([ ['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'}], From 6608a79769ff03543ab4ed2f2cead3410d7d7e99 Mon Sep 17 00:00:00 2001 From: admon84 Date: Tue, 22 Sep 2020 21:33:36 -0600 Subject: [PATCH 3/3] feat: invert logic (#141) Co-authored-by: Jef LeCompte --- src/store/lookup.ts | 128 +++++++++++++++++++++------------ src/store/model/adorama.ts | 6 +- src/store/model/amazon-ca.ts | 10 ++- src/store/model/amazon.ts | 11 ++- src/store/model/asus.ts | 11 ++- src/store/model/bandh.ts | 6 +- src/store/model/bestbuy.ts | 5 +- src/store/model/evga-eu.ts | 5 +- src/store/model/evga.ts | 5 +- src/store/model/microcenter.ts | 5 +- src/store/model/newegg-ca.ts | 10 ++- src/store/model/newegg.ts | 10 ++- src/store/model/nvidia.ts | 5 +- src/store/model/officedepot.ts | 12 +++- src/store/model/store.ts | 10 ++- src/store/model/zotac.ts | 11 ++- 16 files changed, 177 insertions(+), 73 deletions(-) diff --git a/src/store/lookup.ts b/src/store/lookup.ts index 73bf8af1f2..9cddb10f4c 100644 --- a/src/store/lookup.ts +++ b/src/store/lookup.ts @@ -1,8 +1,8 @@ -import {Browser, Response} from 'puppeteer'; +import {Browser, Page, Response} from 'puppeteer'; +import {Link, Store} from './model'; import {closePage, delay, getSleepTime} from '../util'; import {Config} from '../config'; import {Logger} from '../logger'; -import {Store} from './model'; import {includesLabels} from './includes-labels'; import open from 'open'; import {sendNotification} from '../notification'; @@ -57,63 +57,97 @@ async function lookup(browser: Browser, store: Store) { page.setDefaultNavigationTimeout(Config.page.navigationTimeout); await page.setUserAgent(Config.page.userAgent); - const graphicsCard = `${link.brand} ${link.model} ${link.series}`; - - let response: Response | null; try { - response = await page.goto(link.url, {waitUntil: 'networkidle0'}); - } catch { - Logger.error(`✖ [${store.name}] ${graphicsCard} skipping; timed out`); - await closePage(page); - continue; + await lookupCard(browser, store, page, link); + } catch (error) { + Logger.error(`✖ [${store.name}] ${link.brand} ${link.model} - ${error.message as string}`); } - const bodyHandle = await page.$('body'); - const textContent = await page.evaluate(body => body.textContent, bodyHandle); + await closePage(page); + } + /* eslint-enable no-await-in-loop */ +} - Logger.debug(textContent); +async function lookupCard(browser: Browser, store: Store, page: Page, link: Link) { + const response: Response | null = await page.goto(link.url, {waitUntil: 'networkidle0'}); + const graphicsCard = `${link.brand} ${link.model}`; + + if (await lookupCardInStock(store, page)) { + Logger.info(`🚀🚀🚀 [${store.name}] ${graphicsCard} IN STOCK 🚀🚀🚀`); + Logger.info(link.url); + if (Config.page.inStockWaitTime) { + inStock[store.name] = true; + setTimeout(() => { + inStock[store.name] = false; + }, 1000 * Config.page.inStockWaitTime); + } - if (includesLabels(textContent, store.labels.outOfStock)) { - Logger.info(`✖ [${store.name}] still out of stock: ${graphicsCard}`); - } else if (store.labels.bannedSeller && includesLabels(textContent, store.labels.bannedSeller)) { - Logger.warn(`✖ [${store.name}] banned seller detected: ${graphicsCard}. skipping...`); - } else if (store.labels.captcha && includesLabels(textContent, store.labels.captcha)) { - Logger.warn(`✖ [${store.name}] CAPTCHA from: ${graphicsCard}. Waiting for a bit with this store...`); - await delay(getSleepTime()); - } else if (response && response.status() === 429) { - Logger.warn(`✖ [${store.name}] Rate limit exceeded: ${graphicsCard}`); - } else { - Logger.info(`🚀🚀🚀 [${store.name}] ${graphicsCard} IN STOCK 🚀🚀🚀`); - Logger.info(link.url); - if (Config.page.inStockWaitTime) { - inStock[store.name] = true; - setTimeout(() => { - inStock[store.name] = false; - }, 1000 * Config.page.inStockWaitTime); - } + if (Config.page.capture) { + Logger.debug('ℹ saving screenshot'); + link.screenshot = `success-${Date.now()}.png`; + await page.screenshot({path: link.screenshot}); + } + + const givenUrl = link.cartUrl ? link.cartUrl : link.url; - if (Config.page.capture) { - Logger.debug('ℹ saving screenshot'); - link.screenshot = `success-${Date.now()}.png`; - await page.screenshot({path: link.screenshot}); + if (Config.browser.open) { + if (link.openCartAction === undefined) { + await open(givenUrl); + } else { + link.openCartAction(browser); } + } - const givenUrl = link.cartUrl ? link.cartUrl : link.url; + sendNotification(givenUrl, link); + return; + } - if (Config.browser.open) { - if (link.openCartAction === undefined) { - await open(givenUrl); - } else { - link.openCartAction(browser); - } - } + if (await lookupPageHasCaptcha(store, page)) { + Logger.warn(`✖ [${store.name}] CAPTCHA from: ${graphicsCard}. Waiting for a bit with this store...`); + await delay(getSleepTime()); + return; + } - sendNotification(givenUrl, link); - } + if (response && response.status() === 429) { + Logger.warn(`✖ [${store.name}] Rate limit exceeded: ${graphicsCard}`); + return; + } - await closePage(page); + Logger.info(`✖ [${store.name}] still out of stock: ${graphicsCard}`); +} + +async function lookupCardInStock(store: Store, page: Page) { + const stockHandle = await page.$(store.labels.inStock.container); + + const visible = await page.evaluate(element => element && element.offsetWidth > 0 && element.offsetHeight > 0, stockHandle); + if (!visible) { + return false; } - /* eslint-enable no-await-in-loop */ + + const stockContent = await page.evaluate(element => element.outerHTML, stockHandle); + + Logger.debug(stockContent); + + if (includesLabels(stockContent, store.labels.inStock.text)) { + return true; + } + + return false; +} + +async function lookupPageHasCaptcha(store: Store, page: Page) { + if (!store.labels.captcha) { + return false; + } + + const captchaHandle = await page.$(store.labels.captcha.container); + const captchaContent = await page.evaluate(element => element.textContent, captchaHandle); + + if (includesLabels(captchaContent, store.labels.captcha.text)) { + return true; + } + + return false; } export async function tryLookupAndLoop(browser: Browser, store: Store) { diff --git a/src/store/model/adorama.ts b/src/store/model/adorama.ts index f1fab91c27..81a84e884f 100644 --- a/src/store/model/adorama.ts +++ b/src/store/model/adorama.ts @@ -2,8 +2,10 @@ import {Store} from './store'; export const Adorama: Store = { labels: { - captcha: ['please verify you are a human'], - outOfStock: ['temporarily not available', 'out of stock'] + inStock: { + container: '.buy-section.purchase', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/amazon-ca.ts b/src/store/model/amazon-ca.ts index 21c390507b..6db67f347f 100644 --- a/src/store/model/amazon-ca.ts +++ b/src/store/model/amazon-ca.ts @@ -2,8 +2,14 @@ import {Store} from './store'; export const AmazonCa: Store = { labels: { - captcha: ['enter the characters you see below'], - outOfStock: ['currently unavailable'] + captcha: { + container: 'body', + text: ['enter the characters you see below'] + }, + inStock: { + container: '#desktop_buybox', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/amazon.ts b/src/store/model/amazon.ts index 4cd6ddffd5..a8d4a09565 100644 --- a/src/store/model/amazon.ts +++ b/src/store/model/amazon.ts @@ -2,9 +2,14 @@ import {Store} from './store'; export const Amazon: Store = { labels: { - bannedSeller: ['sports authentics', 'raccoon capitalist', 'gigaparts'], - captcha: ['enter the characters you see below'], - outOfStock: ['currently unavailable', 'available from these sellers'] + captcha: { + container: 'body', + text: ['enter the characters you see below'] + }, + inStock: { + container: '#desktop_buybox', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/asus.ts b/src/store/model/asus.ts index 5d046c733a..dab606f1bd 100644 --- a/src/store/model/asus.ts +++ b/src/store/model/asus.ts @@ -2,9 +2,18 @@ import {Store} from './store'; export const Asus: Store = { labels: { - outOfStock: ['coming soon', 'temporarily sold out'] + inStock: { + container: '#item_add_cart', + text: ['add to cart'] + } }, links: [ + { + brand: 'TEST', + model: 'CARD', + series: 'debug', + url: 'https://store.asus.com/us/item/202003AM280000002/' + }, { brand: 'asus', model: 'tuf oc', diff --git a/src/store/model/bandh.ts b/src/store/model/bandh.ts index 8c3b0f02c7..781951891e 100644 --- a/src/store/model/bandh.ts +++ b/src/store/model/bandh.ts @@ -2,7 +2,10 @@ import {Store} from './store'; export const BAndH: Store = { labels: { - outOfStock: ['notify when available', 'try varying your search terms', 'sorry, an unexpected error has occurred'] + inStock: { + container: 'div[data-selenium="addToCartSection"]', + text: ['add to cart'] + } }, links: [ { @@ -61,7 +64,6 @@ export const BAndH: Store = { series: '3080', url: 'https://www.bhphotovideo.com/c/product/1593646-REG/msi_geforce_rtx_3080_ventus.html' } - ], name: 'bandh' }; diff --git a/src/store/model/bestbuy.ts b/src/store/model/bestbuy.ts index 9f7b8e4bd2..c59e991427 100644 --- a/src/store/model/bestbuy.ts +++ b/src/store/model/bestbuy.ts @@ -2,7 +2,10 @@ import {Store} from './store'; export const BestBuy: Store = { labels: { - outOfStock: ['sold out', 'coming soon'] + inStock: { + container: '.v-m-bottom-g', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/evga-eu.ts b/src/store/model/evga-eu.ts index da3a70c02a..ae2e89b575 100644 --- a/src/store/model/evga-eu.ts +++ b/src/store/model/evga-eu.ts @@ -2,7 +2,10 @@ import {Store} from './store'; export const EvgaEu: Store = { labels: { - outOfStock: ['tbd', 'out of stock', 'error reaching the evga website', 'oops! something broke.'] + inStock: { + container: '.product-buy-specs', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/evga.ts b/src/store/model/evga.ts index db98eea75e..58ea92c074 100644 --- a/src/store/model/evga.ts +++ b/src/store/model/evga.ts @@ -2,7 +2,10 @@ import {Store} from './store'; export const Evga: Store = { labels: { - outOfStock: ['out of stock', 'error reaching the evga website', 'oops! something broke.'] + inStock: { + container: '.product-buy-specs', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/microcenter.ts b/src/store/model/microcenter.ts index 91dde35d6e..4baab20a93 100644 --- a/src/store/model/microcenter.ts +++ b/src/store/model/microcenter.ts @@ -2,7 +2,10 @@ import {Store} from './store'; export const MicroCenter: Store = { labels: { - outOfStock: ['sold out'] + inStock: { + container: '#cart-options', + text: ['(in stock)'] + } }, links: [ { diff --git a/src/store/model/newegg-ca.ts b/src/store/model/newegg-ca.ts index 3940f00955..aaedf49947 100644 --- a/src/store/model/newegg-ca.ts +++ b/src/store/model/newegg-ca.ts @@ -2,8 +2,14 @@ import {Store} from './store'; export const NewEggCa: Store = { labels: { - captcha: ['are you a human?'], - outOfStock: ['auto notify', 'item is currently out of stock', 'service unavailable'] + captcha: { + container: 'body', + text: ['are you a human?'] + }, + inStock: { + container: '#landingpage-cart .btn-primary span', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/newegg.ts b/src/store/model/newegg.ts index fd442ca9d6..38ccb2b623 100644 --- a/src/store/model/newegg.ts +++ b/src/store/model/newegg.ts @@ -2,8 +2,14 @@ import {Store} from './store'; export const NewEgg: Store = { labels: { - captcha: ['are you a human?'], - outOfStock: ['auto notify', 'item is currently out of stock', 'service unavailable', 'we are currently experiencing problems on our server'] + captcha: { + container: 'body', + text: ['are you a human?'] + }, + inStock: { + container: '#landingpage-cart .btn-primary span', + text: ['add to cart'] + } }, links: [ { diff --git a/src/store/model/nvidia.ts b/src/store/model/nvidia.ts index d27d203b27..f588a26abd 100644 --- a/src/store/model/nvidia.ts +++ b/src/store/model/nvidia.ts @@ -35,7 +35,10 @@ export const regionInfos = new Map([ export const Nvidia: Store = { labels: { - outOfStock: ['product_inventory_out_of_stock', 'rate limit exceeded', 'request timeout'] + inStock: { + container: 'body', + text: ['product_inventory_in_stock'] + } }, links: generateLinks(), name: 'nvidia', diff --git a/src/store/model/officedepot.ts b/src/store/model/officedepot.ts index 5dd62c646c..e02a02ce6d 100644 --- a/src/store/model/officedepot.ts +++ b/src/store/model/officedepot.ts @@ -2,15 +2,21 @@ import {Store} from './store'; export const OfficeDepot: Store = { labels: { - captcha: ['please verify you are a human'], - outOfStock: ['out of stock for delivery', 'out of stock', 'we are unable to process your last request'] + captcha: { + container: 'body', + text: ['please verify you are a human'] + }, + inStock: { + container: '#productPurchase', + text: ['add to cart'] + } }, links: [ { brand: 'TEST', model: 'CARD', series: 'debug', - url: 'https://www.officedepot.com/a/products/7189374/PNY-GeForce-RTX-3080-10GB-GDDR6X/' + url: 'https://www.officedepot.com/a/products/4652239/EVGA-GeForce-RTX-2060-Graphic-Card/' }, { brand: 'pny', diff --git a/src/store/model/store.ts b/src/store/model/store.ts index d3fec778ea..1c25921775 100644 --- a/src/store/model/store.ts +++ b/src/store/model/store.ts @@ -1,5 +1,10 @@ import {Browser} from 'puppeteer'; +export interface Element { + container: string; + text: string[]; +} + export interface Link { series: string; brand: string; @@ -11,9 +16,8 @@ export interface Link { } export interface Labels { - outOfStock: string[]; - captcha?: string[]; - bannedSeller?: string[]; + captcha?: Element; + inStock: Element; } export interface Store { diff --git a/src/store/model/zotac.ts b/src/store/model/zotac.ts index 77542da10c..da8ff01ce7 100644 --- a/src/store/model/zotac.ts +++ b/src/store/model/zotac.ts @@ -2,9 +2,18 @@ import {Store} from './store'; export const Zotac: Store = { labels: { - outOfStock: ['out of stock', 'this process is automatic'] + inStock: { + container: '.add-to-cart-wrapper', + text: ['add to cart'] + } }, links: [ + { + brand: 'TEST', + model: 'CARD', + series: 'debug', + url: 'https://store.zotac.com/zotac-gaming-geforce-rtx-2060-twin-fan-zt-t20600f-10m' + }, { brand: 'zotac', model: 'trinity',