Brickowl version

brickowl
Edward Peterson 1 year ago
parent 636d9056e6
commit 9a0f9d8a06

@ -12,8 +12,9 @@ module.exports = class {
this.sellerUsername = listing.strSellerUsername; this.sellerUsername = listing.strSellerUsername;
this.sellerStoreName = listing.strStorename; this.sellerStoreName = listing.strStorename;
this.instantCheckout = listing.instantCheckout; this.instantCheckout = listing.instantCheckout;
// this.internalPartId = listing.internalPartId;
this.legoPartNumber = null; this.legoPartNumber = null;
this.blPartNumber = null; this.blPartNumber = listing.boid;
this.calculatedPrice = 0 this.calculatedPrice = 0
this.prices = [ this.prices = [
{ {
@ -55,7 +56,7 @@ module.exports = class {
return null; return null;
} }
toString(){ toString(){
return `${this.legoPartNumber} (${this.quantity}) -${this.colorId}- @ $${this.getPrice(this.quantity)}` return `${this.legoPartNumber || this.blPartNumber} (${this.quantity}) -${this.colorId}- @ $${this.getPrice(this.quantity)}`
} }
} }
@ -65,6 +66,9 @@ function parseMinBuy(value){
return parseDollars(value); return parseDollars(value);
} }
function parseDollars(val) { function parseDollars(val) {
if(parseFloat(val) !== NaN) {
return parseFloat(val)
}
const regexed = priceRegex.exec(val); const regexed = priceRegex.exec(val);
const stringPrice = regexed?.groups?.price; const stringPrice = regexed?.groups?.price;
if(!stringPrice){ if(!stringPrice){

@ -0,0 +1,168 @@
const superagent = require('superagent');
const LotListing = require('./LotListing')
const brickLinkAgent = superagent.agent().set('Accept', 'application/json');
const htmlParser = require('node-html-parser');
const _ = require('lodash');
const brickOwlKey = process.env.brickowlKey
module.exports = {
getInternalPartId(legoPartNumber, type = 'Part') {
return new Promise((resolve, reject) => {
brickLinkAgent.get(`https://api.brickowl.com/v1/catalog/id_lookup`)
.query({
key: brickOwlKey,
id: legoPartNumber,
type: type,
})
// .set('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0')
.then((response => {
if(response.statusCode !== 200) throw response.statusCode;
const possibleIds = _.get(response, 'body.boids');
if(possibleIds.length != 1) {
throw new Error(`Unable to uniquely get internal id for part ${legoPartNumber}, found ${possibleIds}`)
}
return resolve(possibleIds[0]);
})).catch(error => {
reject(error);
})
})
},
getListings(internalPartId, colorId) {
return new Promise(async (resolve, reject) => {
await new Promise((resolve) => setTimeout(resolve, Math.random() * 500 + 100));
var query = {
key: brickOwlKey,
boid: internalPartId,
country: 'US'
// loc: 'US',
// rpp: 100
// cond: 'U'
}
if(colorId){
query.color = colorId
}
brickLinkAgent.get(`https://api.brickowl.com/v1/catalog/availability`)
.query(query)
// .set('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0')
.set('Accept', 'application/json').then(response => {
if(!response.body) {
console.log(response.body, query)
return reject('no list found');
}
//convert to array
const list = _.values(response.body).filter(lot => {
return lot.country === 'US' && lot.base_currency === 'USD'
});
resolve(list.map(l => new LotListing({
idInv: l.lot_id,
strDesc: l.url,
codeNew: l.con === 'new' ? 'N' : 'U',
codeComplete: null,
n4Qty: parseInt(l.qty),
strColor: 'UHHHH',
idColor: l.boid.includes('-') ? l.boid.substring(l.boid.indexOf('-') + 1) : 0,
mMinBuy: l.minimum_order,
strSellerUsername: l.store_id,
strStorename: l.store_name,
instantCheckout: false,
legoPartNumber: 'UHHHH',
boid: l.boid,
mDisplaySalePrice: l.price
})));
}).catch(error => {
console.error('HTTP error from getListings', error)
reject(error);
})
})
},
getListingsByLegoPartNumber(legoPartNumber, colorId){
return module.exports.getInternalPartId(legoPartNumber).then((blPartNumber) => module.exports.getListings(blPartNumber, colorId)).then(listings => {
return listings.map(listing => {
listing.legoPartNumber = legoPartNumber
return listing;
})
})
},
getStoreId(sellerUsername) {
return new Error('Not implemented');
return new Promise((resolve, reject) => {
brickLinkAgent.get(`https://store.bricklink.com/${sellerUsername}`)
.then((response => {
const storeIdRegex = /username:\s+'.+',\s+id:\s+(?<storeId>\d+)/gm;
const matches = storeIdRegex.exec(response.text);
resolve(matches.groups?.storeId)
})).catch(error => {
console.error('HTTP error from getStoreId', error)
reject(error);
})
})
},
getSetInventory(setId) {
return new Promise(async (resolve, reject) => {
const setBoid = await module.exports.getInternalPartId(setId, 'Set')
brickLinkAgent.get(`https://api.brickowl.com/v1/catalog/inventory`)
.query({
key: brickOwlKey,
boid: setBoid
})
.then((response => {
return resolve(_.get(response, 'body.inventory', []).map(type => {
return {
partNumber: null,
internalPartId: type.boid,
colorId: null,
quantity: type.quantity
}
}))
// var root = htmlParser.parse(response.text);
// const rows = root.querySelectorAll('.IV_ITEM')
// const parsed = rows.map(row => {
// const partRegex = /\/v2\/catalog\/catalogitem.page\?P=(?<legoPartNumber>.+)&idColor=(?<colorId>\d+)/gm;
// const quantityRegex = /<TD ALIGN="RIGHT">\&nbsp;(?<quantity>\d+)/gm
// const matches = partRegex.exec(row.innerHTML);
// const quantityMatches = quantityRegex.exec(row.innerHTML);
// if(!matches) return null;
// return {
// partNumber: matches.groups?.legoPartNumber,
// colorId: parseInt(matches.groups?.colorId),
// quantity: parseInt(quantityMatches.groups?.quantity)
// }
// }).filter(r => r !== null);
resolve(parsed)
})).catch(error => {
console.error('HTTP error from getStoreId', error)
reject(error);
})
})
},
getDetails(boid) {
return new Promise((resolve, reject) => {
brickLinkAgent.get(`https://api.brickowl.com/v1/catalog/lookup`)
.query({
key: brickOwlKey,
boid: boid
})
.then((response => {
resolve(response.body)
})).catch(error => {
console.error('HTTP error from getStoreId', error)
reject(error);
})
})
}
}

File diff suppressed because one or more lines are too long

@ -1,53 +1,82 @@
const env = require('dotenv');
env.config();
const brickLinkAPI = require('./bricklink'); const brickLinkAPI = require('./bricklink');
const brickowlAPI = require('./brickowl');
const _ = require('lodash'); const _ = require('lodash');
const util = require('util'); const util = require('util');
const Cart = require('./Cart'); const Cart = require('./Cart');
function getRandomizedDelay(){ function getRandomizedDelay(){
return Math.random() * 500 + 500; return Math.random() * 500 + 500;
} }
async function getListings(order) { async function getListings(order) {
const results = [] // const results = []
for (var i = 0; i < order.length; i++) { var promises = order.map(lot => {
const lot = order[i]; return new Promise(async (resolve, reject) => {
try { try {
var listings = await brickLinkAPI.getListingsByLegoPartNumber(lot.partNumber, lot.colorId); if(!lot.partNumber) {
console.log(listings.length + 'Listings found for', lot.partNumber, `(#${lot.colorId}#)`); const details = await brickowlAPI.getDetails(lot.internalPartId);
lot.colorId = details.color_id;
lot.partNumber = _.get(_.find(details.ids, (i => i.type === 'design_id')), 'id', null)
lot.desc = details.name
if(!lot.partNumber) {
console.log('Unable to get lego part number for ', lot.internalPartId)
lot.partNumber = details.name
}
}
// var listings = await brickowlAPI.getListingsByLegoPartNumber(lot.partNumber, lot.colorId);
var listings = await brickowlAPI.getListings(lot.internalPartId, lot.colorId);
console.log(listings.length + 'Listings found for', lot.internalPartId, `(#${lot.colorId}#)`);
const lotsWithEnoughItems = listings.filter(l => l.quantity >= lot.quantity); const lotsWithEnoughItems = listings.filter(l => l.quantity >= lot.quantity);
results.push(lotsWithEnoughItems.map(lota => { // if(lotsWithEnoughItems.length === 0) return reject('Unable to source part ' + lot.blPartNumber)
resolve(lotsWithEnoughItems.map(lota => {
//update the quantity to only what we need //update the quantity to only what we need
lota.quantity = lot.quantity; lota.quantity = lot.quantity;
return lota; return lota;
})); }));
//Wait added to not cause a 403. dont want to dos bricklink //Wait added to not cause a 403. dont want to dos bricklink
await new Promise(resolve => setTimeout(resolve, getRandomizedDelay())); // await new Promise(resolve => setTimeout(resolve, getRandomizedDelay()));
} catch (error) { } catch (error) {
console.error('Unable to source: ', lot.partNumber, error) console.error('Unable to source: ', lot.blPartNumber, error)
reject(error);
} }
})
});
// for (var i = 0; i < order.length; i++) {
// const lot = order[i];
// }
return await Promise.all(promises);
} }
return results; brickowlAPI.getSetInventory('2996-1').then((inventory) => {
} // console.log('Inventory', inventory)
brickLinkAPI.getSetInventory('4990-1').then((inventory) => { getListings(inventory).then(async parts => {
getListings(inventory).then(parts => {
var carts = []; var carts = [];
const possibleLots = _.flatten(parts); const possibleLots = _.flatten(parts);
possibleLots.forEach(lot => { possibleLots.forEach(lot => {
let logging = false;
const group = _.findIndex(carts, l => l.sellerUsername === lot.sellerUsername); const group = _.findIndex(carts, l => l.sellerUsername === lot.sellerUsername);
if (group === -1) { if (group === -1) {
const newGroup = new Cart(lot.sellerUsername, lot.minBuy, [lot]); const newGroup = new Cart(lot.sellerUsername, lot.minBuy, [lot]);
carts.push(newGroup); carts.push(newGroup);
} else { } else {
carts[group].addToCart(lot); carts[group].addToCart(lot);
} }
}) })
//sometimes sellers make multiple listings of the same part in the same color. filter for the cheapest //sometimes sellers make multiple listings of the same part in the same color. filter for the cheapest
carts = carts.map(cart => { carts = carts.map(cart => {
const groupedLots = _.groupBy(cart.lots, (lot) => `${lot.legoPartNumber}-${lot.colorId}`); const groupedLots = _.groupBy(cart.lots, (lot) => `${lot.blPartNumber}-${lot.colorId}`);
const listingsToRemove = []; const listingsToRemove = [];
_.keys(groupedLots).forEach((partType) => { _.keys(groupedLots).forEach((partType) => {
if (groupedLots[partType].length <= 1) return; if (groupedLots[partType].length <= 1) return;
@ -71,10 +100,13 @@ brickLinkAPI.getSetInventory('4990-1').then((inventory) => {
_.differenceBy(cart.lots, listingsToRemove, (a) => a.listingId)) _.differenceBy(cart.lots, listingsToRemove, (a) => a.listingId))
}) })
const fs = require('fs');
await fs.writeFileSync('./carts.json', JSON.stringify(carts))
//throw out any carts that won't meet min buy //throw out any carts that won't meet min buy
var aboveMinBuy = carts.filter((cart) => cart.getPrice() >= cart.minBuy); var aboveMinBuy = carts.filter((cart) => cart.getPrice() >= cart.minBuy);
console.log(`Threw out ${carts.length - aboveMinBuy.length} carts due to min purchase requirements.`)
console.log(`Found ${aboveMinBuy.length} possible carts`); console.log(`Found ${aboveMinBuy.length} possible carts`);
@ -89,17 +121,21 @@ brickLinkAPI.getSetInventory('4990-1').then((inventory) => {
var calculatedCarts = []; var calculatedCarts = [];
while (currentlyUnsourcedParts.length > 0) { while (currentlyUnsourcedParts.length > 0) {
console.log(`Searching for parts to cover ${currentlyUnsourcedParts.length} lots...`, currentlyUnsourcedParts) console.log(`Searching for parts to cover ${currentlyUnsourcedParts.length} lots...`)
const bestAddition = getCartWithBestCoverage(aboveMinBuy, currentlyUnsourcedParts); const bestAddition = getCartWithBestCoverage(aboveMinBuy, currentlyUnsourcedParts);
if(!bestAddition) {
//run out of coverage options
break;
}
calculatedCarts.push(bestAddition); calculatedCarts.push(bestAddition);
console.log(`Added new cart to cover ${bestAddition.lots.length} lots.`, bestAddition); console.log(`Added new cart to cover ${bestAddition.lots.length} lots for ${bestAddition.getPrice()}`);
//check for duplicated lots across carts and keep the lowest price one, so long as it doesn't push us below min buy //check for duplicated lots across carts and keep the lowest price one, so long as it doesn't push us below min buy
const lotsFromAllCarts = calculatedCarts.reduce((acc, cart) => { acc.push(...cart.lots); return acc; }, []) const lotsFromAllCarts = calculatedCarts.reduce((acc, cart) => { acc.push(...cart.lots); return acc; }, [])
const groupedLots = _.groupBy(lotsFromAllCarts, (lot) => `${lot.legoPartNumber}-${lot.colorId}`); const groupedLots = _.groupBy(lotsFromAllCarts, (lot) => `${lot.blPartNumber}`);
const listingsToRemove = []; const listingsToRemove = [];
_.keys(groupedLots).forEach((partType) => { _.keys(groupedLots).forEach((partType) => {
const legoPartNumber = partType.split('-')[0]; const legoPartNumber = partType.split('-')[0];
@ -127,7 +163,8 @@ brickLinkAPI.getSetInventory('4990-1').then((inventory) => {
//keep this cart, remove the item from the others. //keep this cart, remove the item from the others.
//get the listing in this cart //get the listing in this cart
const cart = cartsAtLargestSize[0]; const cart = cartsAtLargestSize[0];
const keepingLot = cart.lots.find((lot) => lot.legoPartNumber == legoPartNumber && lot.colorId == colorId); const keepingLot = cart.lots.find((lot) => lot.blPartNumber == partType);
// console.log(cart, keepingLot, legoPartNumber, colorId)
listingsToRemove.push(...(groupedLots[partType].filter(lot => lot.listingId !== keepingLot.listingId).map(i => i.listingId))) listingsToRemove.push(...(groupedLots[partType].filter(lot => lot.listingId !== keepingLot.listingId).map(i => i.listingId)))
return; return;
} }
@ -149,13 +186,16 @@ brickLinkAPI.getSetInventory('4990-1').then((inventory) => {
} }
// console.log('Final cart', util.inspect(calculatedCarts, false, null, true)); // console.log('Final cart', util.inspect(calculatedCarts, false, null, true));
console.log('Total price without shipping: ', calculatedCarts.reduce((acc, cart) => acc + cart.getPrice(), 0), 'across', calculatedCarts.length, 'stores'); console.log('Total price without shipping: ', calculatedCarts.reduce((acc, cart) => acc + cart.getPrice(), 0), 'across', calculatedCarts.length, 'stores');
if(currentlyUnsourcedParts.length > 0) {
console.log('Some lots couldnt be sourced from a single seller', currentlyUnsourcedParts)
}
calculatedCarts.forEach((cart) => console.log(cart.toString())); calculatedCarts.forEach((cart) => console.log(cart.toString()));
}) })
}) })
function getCartWithBestCoverage(carts, partsList) { function getCartWithBestCoverage(carts, partsList) {
var coverages = carts.map(cart => { var coverages = carts.map(cart => {
const itemsNotInThisCart = partsList.filter(item => { const itemsNotInThisCart = partsList.filter(item => {
return _.findIndex(cart.lots, lot => lot.legoPartNumber === item.partNumber && lot.colorId === item.colorId) === -1; return _.findIndex(cart.lots, lot => lot.blPartNumber === item.internalPartId && lot.colorId === item.colorId) === -1;
}) })
if (itemsNotInThisCart.length === 0) { if (itemsNotInThisCart.length === 0) {
//ONE STORE SOLUTION FOUND, NICE! //ONE STORE SOLUTION FOUND, NICE!
@ -173,7 +213,9 @@ function getCartWithBestCoverage(carts, partsList) {
}).sort((a, b) => a.getPrice() - b.getPrice()); }).sort((a, b) => a.getPrice() - b.getPrice());
if(bestCoverage === partsList.length){ if(bestCoverage === partsList.length){
//no carts are providing coverage. //no carts are providing coverage.
throw new Error('Cannot fulfill order, lacking coverage'); console.log('Lacking coverage for the following parts', partsList)
// throw new Error('Cannot fulfill order, lacking coverage');
return false;
} }
// if(Math.min(...coverages)) === ) // if(Math.min(...coverages)) === )
// const bestCoverageIndex = coverages.indexOf(); // const bestCoverageIndex = coverages.indexOf();
@ -181,9 +223,16 @@ function getCartWithBestCoverage(carts, partsList) {
} }
function getUnsourcedParts(carts, partsList) { function getUnsourcedParts(carts, partsList) {
const allLotsFromAllCarts = _.uniqBy(carts.reduce((acc, cart) => { acc.push(...cart.lots); return acc; }, []), (lot) => `${lot.legoPartNumber}_${lot.colorId}`); const allLotsFromAllCarts = _.uniqBy(carts.reduce((acc, cart) => { acc.push(...cart.lots); return acc; }, []), (lot) => `${lot.blPartNumber}_${lot.colorId}`);
console.log(allLotsFromAllCarts.map(a => a.blPartNumber).join(','));
return partsList.filter((part) => { return partsList.filter((part) => {
return allLotsFromAllCarts.findIndex((lot) => lot.legoPartNumber === part.partNumber && lot.colorId === part.colorId) === -1 const coverageIndex = allLotsFromAllCarts.findIndex((lot) => {
if(lot.blPartNumber === "868124-58")
console.log(lot, part)
return lot.blPartNumber === part.internalPartId && (!lot.colorId || lot.colorId === part.colorId)
})
if(coverageIndex !== -1) console.log(`Coverage for ${part.internalPartId} found!`)
return coverageIndex === -1
}) })
} }

455
package-lock.json generated

@ -1,8 +1,456 @@
{ {
"name": "bricklinkaggregator", "name": "bricklinkaggregator",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": {
"": {
"name": "bricklinkaggregator",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"dotenv": "^16.4.5",
"lodash": "^4.17.21",
"node-html-parser": "^6.1.4",
"superagent": "^8.0.6"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"node_modules/cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ=="
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
"integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.1"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/entities": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formidable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"dependencies": {
"dezalgo": "^1.0.4",
"hexoid": "^1.0.0",
"once": "^1.4.0",
"qs": "^6.11.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-intrinsic": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
"integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"bin": {
"he": "bin/he"
}
},
"node_modules/hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
"engines": {
"node": ">=8"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/node-html-parser": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.4.tgz",
"integrity": "sha512-3muP9Uy/Pz7bQa9TNYVQzWJhNZMqyCx7xJle8kz2/y1UgzAUyXXShc1IcPaJy6u07CE3K5rQcRwlvHzmlySRjg==",
"dependencies": {
"css-select": "^5.1.0",
"he": "1.2.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/superagent": {
"version": "8.0.6",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz",
"integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==",
"deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net",
"dependencies": {
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.3",
"debug": "^4.3.4",
"fast-safe-stringify": "^2.1.1",
"form-data": "^4.0.0",
"formidable": "^2.1.1",
"methods": "^1.1.2",
"mime": "2.6.0",
"qs": "^6.11.0",
"semver": "^7.3.8"
},
"engines": {
"node": ">=6.4.0 <13 || >=14"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
},
"dependencies": { "dependencies": {
"asap": { "asap": {
"version": "2.0.6", "version": "2.0.6",
@ -118,6 +566,11 @@
"domhandler": "^5.0.1" "domhandler": "^5.0.1"
} }
}, },
"dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="
},
"entities": { "entities": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",

@ -9,6 +9,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"dotenv": "^16.4.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"node-html-parser": "^6.1.4", "node-html-parser": "^6.1.4",
"superagent": "^8.0.6" "superagent": "^8.0.6"

@ -0,0 +1,12 @@
[
{
"partNumber": 30295,
"quantity": 1,
"colorId": 53
},
{
"partNumber": 30324,
"quantity": 4,
"colorId": 38
}
]
Loading…
Cancel
Save