diff --git a/index.js b/index.js index 8c8b552..0929d44 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,11 @@ +// Require npm packages. const fetch = require('node-fetch'); -const commandLineArgs = require('command-line-args') +const commandLineArgs = require('command-line-args'); +// Get partNumbers from json file. +const partNumbers = require('./partNumbers.json'); + +// Define command line args accepted. const optionDefinitions = [ { name: 'carrier', type: String, defaultValue: 'TMOBILE' }, { name: 'model', type: String, defaultValue: 'x' }, @@ -10,72 +15,119 @@ const optionDefinitions = [ { name: 'delay', type: Number, defaultValue: 30 }, ]; +// Parse command line args. const options = commandLineArgs(optionDefinitions); -console.log(options); - -const partNumbers = { - 'x': { - 'gray': { - '64': "", - '256': "MQAU2LL/A", - }, - 'silver': { - '64': "", - '256': "", - }, - }, - '8': { - 'gray': { - '64': "", - '256': "MQ932LL/A", - }, - 'silver': { - '64': "", - '256': "", - }, - }, -}; +// Get part number for the specified device. const partNumber = partNumbers[options.model][options.color][options.storage]; +// Construct the endpoint url with the options selected. const endpoint = `https://www.apple.com/shop/retail/pickup-message?pl=true&cppart=${options.carrier}/US&parts.0=${partNumber}&location=${options.zip}`; -// const endpoint = 'https://www.apple.com/shop/retail/pickup-message?pl=true&cppart=TMOBILE/US&parts.0=MQ932LL/A&location=Salem,%20NH'; -let requestsMade = 0; +// Keep track of the last request time. +let lastRequestTimestamp = null; -makeRequest(); +/** + * Update program status display + * + * @param {String} str The string that will be outputed. + */ +function updateStatus() { + if (lastRequestTimestamp === null) { + return; + } -function checkAvailability(data) { + const timeDelta = Date.now() - lastRequestTimestamp; + const timeInSeconds = Math.floor(timeDelta / 1000); + process.stdout.write(`Status: Device not available. Last request made ${timeInSeconds} seconds ago\r`); +} + +/** + * Parse the returned data and find stores where the device is available + * + * @param {Object} data The api response. + * @return {Array} The array of stores where the devices is available. + */ +function processResponse(data) { + // Destructure the stores object out of the body. const { stores } = data.body; - const storesAvailable = stores.filter(store => { - const parts = Object.values(store.partsAvailability); - const part = parts[0]; - const pickupDisplay = part.pickupDisplay; - const availability = pickupDisplay === 'available'; + // Filter out stores that do not have the device available. + const storesAvailable = stores.filter((store) => { + // Select the specified device partNumber. + const part = store.partsAvailability[partNumber]; + // Check that the pickupDisplay property says 'available'. + const availability = part.pickupDisplay === 'available'; + // Return true if the device is available or else false. return availability; }); - if (storesAvailable.length > 0) { - console.log(`Available at ${storesAvailable.length} stores near you:`); - console.log(storesAvailable.map(store => `${store.address.address} which is ${store.storeDistanceWithUnit} away`).reduce((msg,store) => `${msg}\n${store}`)); - process.exit(); - } else { - displayResultInPlace("unavailable"); + // Return an array of stores where the device is available. + return storesAvailable; +} + +/** + * Make a request to the endpoint and get list of stores available + * + * @return {Promise} A promise that should resolve to an array of stores available. + */ +function getStoresAvailable() { + // Update lastRequestTimestamp. + lastRequestTimestamp = Date.now(); + + return fetch(endpoint) + .then(stream => stream.json()) + .catch(error => process.stderr.write('Fetch Error :-S', error)) + .then(data => processResponse(data)); +} + +/** + * Output list of stores where the device is avaliable. + * + * @param {Array} storesAvailable The array of stores where the device is avaliable. + */ +function displayStoresAvailable(storesAvailable) { + // Construct the output string by reducing the storesAvailable array into a string. + const storesAvailableStr = storesAvailable.reduce( + (result, store) => + `${result}\n${store.address.address} which is ${store.storeDistanceWithUnit} away`, + '', + ); + + // Output the message. + process.stdout.write(`The device is currently available at ${storesAvailable.length} stores near you:`); + process.stdout.write(storesAvailableStr); + process.stdout.write('\n'); +} + +/** + * The main program loop + * + * Continuously check for the device availability until it is available somewhere. + */ +async function requestLoop() { + // Fetch the storesAvailable array. + const storesAvailable = await getStoresAvailable(); + + if (storesAvailable.length === 0) { + // If the array is empty, update the status and after the + // specified options.delay amount of seconds, try again. setTimeout(() => { - makeRequest(); - requestsMade++; + requestLoop(); }, options.delay * 1000); + } else { + // The device is available. Show that information to the user and exit the program. + displayStoresAvailable(storesAvailable); + process.exit(); } } -function makeRequest() { - fetch(endpoint) - .then(stream => stream.json()) - .then(data => checkAvailability(data)) - .catch(error => console.log('Fetch Error :-S', error)); -} +// Display program started message. +process.stdout.write('Starting program with the following settings:\n'); +process.stdout.write(`${JSON.stringify(options, null, 2)}\n`); -function displayResultInPlace(data) { - process.stdout.write(`${data} --- req: ${requestsMade}\r`); -} \ No newline at end of file +// Kick off program. +setInterval(() => { + updateStatus(); +}, 1000); +requestLoop(); diff --git a/partNumbers.json b/partNumbers.json new file mode 100644 index 0000000..3e95beb --- /dev/null +++ b/partNumbers.json @@ -0,0 +1,22 @@ +{ + "x": { + "gray": { + "64": "", + "256": "MQAU2LL/A" + }, + "silver": { + "64": "", + "256": "" + } + }, + "8": { + "gray": { + "64": "", + "256": "MQ932LL/A" + }, + "silver": { + "64": "", + "256": "" + } + } +}