├── package.json ├── README.md ├── items.json ├── LICENSE.md ├── index.js └── .gitignore /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bungholio", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "bluebird": "^3.7.2", 13 | "dotenv": "^8.2.0", 14 | "moment": "^2.24.0", 15 | "puppeteer": "^2.1.1", 16 | "twilio": "^3.41.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bungholio 2 | 3 | Monitors Amazon and sends a text when watched products (like TP) become available. It will send a text at most once per day per product. 4 | 5 | **Note** 6 | 7 | Requires a Twilio account. 8 | 9 | ## Installation 10 | 11 | 1. Clone this repo. 12 | 2. `npm install` or `yarn install` 13 | 3. Modify items.json with the name and url of products you want to watch. 14 | 4. Create a .env file with the following Twilio attributes 15 | 16 | ``` 17 | accountSid=twilioSid 18 | authToken=TwilioAuthToken 19 | twilioFrom='+yourTwilioPhoneNumber' 20 | twilioTo='+phoneNumberToText' 21 | ``` 22 | 23 | 5. Run it 24 | 25 | ``` 26 | node index.js 27 | ``` 28 | -------------------------------------------------------------------------------- /items.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "name": "Charmin TP", 5 | "url": "https://www.amazon.com/gp/product/B0798C1NYR/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1" 6 | }, 7 | { 8 | "name": "Presto Paper Towels", 9 | "url": "https://www.amazon.com/gp/product/B07QY8P3B1/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1" 10 | }, 11 | { 12 | "name": "Angel Soft TP", 13 | "url": "https://www.amazon.com/Angel-Soft-Toilet-Double-Regular/dp/B07SPY4HM8/ref=sr_1_1?crid=9RJ5GS2DWFKQ&keywords=toilet+paper&qid=1585684440&sprefix=toil%2Chpc%2C143&sr=8-1" 14 | }, 15 | { 16 | "name": "Cornholio T-Shirt", 17 | "url": "https://www.amazon.com/Animation-Shops-Butthead-Cornholio-T-Shirt-Large/dp/B06XBZ9XXB/ref=sr_1_4?crid=27E4X7EJMFFM2&dchild=1&keywords=cornholio+t-shirt&qid=1585716119&sprefix=cornholio%2Caps%2C213&sr=8-4", 18 | "found": "2020-03-31T21:44:33.609Z" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 John Titus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const Promise = require('bluebird'); 3 | const fs = require('fs'); 4 | const moment = require('moment'); 5 | 6 | require('dotenv').config(); 7 | 8 | const client = require('twilio')(process.env.accountSid, process.env.authToken); 9 | 10 | const file = require('./items'); 11 | 12 | const items = file.items; 13 | 14 | async function checkItem(page, item) { 15 | console.log(`Checking ${item.name}`); 16 | await page.goto(item.url); 17 | 18 | const canAdd = await page.$('#add-to-cart-button'); 19 | const notInStock = (await page.content()).match(/in stock on/gi); 20 | 21 | return canAdd && !notInStock; 22 | } 23 | 24 | async function sendSMS(item) { 25 | return client.messages.create({ 26 | body: `${item.name} available! ${item.url}`, 27 | from: process.env.twilioFrom, 28 | to: process.env.twilioTo 29 | }); 30 | } 31 | 32 | async function run() { 33 | console.log(''); 34 | console.log(`Starting at ${moment().toISOString()}`); 35 | const browser = await puppeteer.launch(); 36 | 37 | const page = await browser.newPage(); 38 | 39 | await page.setViewport({ 40 | width: 1680, 41 | height: 1050 42 | }); 43 | 44 | await Promise.map( 45 | items, 46 | async item => { 47 | const oneDayAgo = moment().subtract(1, 'days'); 48 | if (!item.found || moment(item.found).isBefore(oneDayAgo)) { 49 | const available = await checkItem(page, item); 50 | 51 | if (available) { 52 | item.found = moment().toISOString(); 53 | console.log(`${item.name} is available.`); 54 | await sendSMS(item); 55 | } else { 56 | console.log(`${item.name} is not available.`); 57 | } 58 | console.log('Waiting...'); 59 | return Promise.delay(4000); 60 | } 61 | }, 62 | { concurrency: 1 } 63 | ); 64 | 65 | const update = { items: items }; 66 | console.log('finishing...'); 67 | fs.writeFileSync('items.json', JSON.stringify(update, null, 4)); 68 | await browser.close(); 69 | console.log('browser closed'); 70 | return; 71 | } 72 | 73 | run(); 74 | 75 | setInterval(async function() { 76 | await run(); 77 | console.log('back'); 78 | console.log('waiting 15 minutes'); 79 | }, 15 * 60 * 1000); 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and not Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | # Stores VSCode versions used for testing VSCode extensions 108 | .vscode-test 109 | 110 | # yarn v2 111 | 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .pnp.* 116 | --------------------------------------------------------------------------------