Files
iphone-x-availability-node-cli/index.js

135 lines
4.3 KiB
JavaScript

// Require npm packages.
const fetch = require('node-fetch');
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' },
{ name: 'color', type: String, defaultValue: 'gray' },
{ name: 'storage', type: String, defaultValue: '256' },
{ name: 'zip', type: String, defaultOption: true },
{ name: 'delay', type: Number, defaultValue: 30 },
];
// Parse command line args.
const options = commandLineArgs(optionDefinitions);
// Get part number for the specified device.
const partNumber =
partNumbers[options.model][options.carrier.toUpperCase()][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}`;
// Keep track of the last request time.
let lastRequestTimestamp = null;
/**
* Update program status display
*
* @param {String} str The string that will be outputed.
*/
function updateStatus() {
if (lastRequestTimestamp === null) {
return;
}
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;
// 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;
});
// 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(() => {
requestLoop();
}, options.delay * 1000);
} else {
// The device is available. Show that information to the user and exit the program.
displayStoresAvailable(storesAvailable);
process.exit();
}
}
// Display program started message.
process.stdout.write('Starting program with the following settings:\n');
process.stdout.write(`${JSON.stringify(options, null, 2)}\n`);
// Kick off program.
setInterval(() => {
updateStatus();
}, 1000);
requestLoop();