diff --git a/Cart.js b/Cart.js new file mode 100644 index 0000000..6f5d7d9 --- /dev/null +++ b/Cart.js @@ -0,0 +1,29 @@ +module.exports = class { + constructor(sellerUsername, minBuy, lots) { + this.sellerUsername = sellerUsername; + this.minBuy = minBuy; + this.lots = lots || []; + } + addToCart(lot) { + this.lots.push(lot); + } + removeFromCart(partNumber, colorId){ + this.lots = this.lots.filter(lot => lot.legoPartNumber !== partNumber && lot.colorId !== colorId) + } + removeListingIdFromCart(listingId) { + if(this.hasListingId(listingId)) + this.lots.splice(this.lots.findIndex((lot) => lot.listingId === listingId), 1); + } + getPrice(){ + return this.lots.reduce((totalPrice, lot) => totalPrice + lot.getPrice(lot.quantity), 0); + + } + toString() { + return `Store: ${this.sellerUsername} +Price: ${this.getPrice()} +${this.lots.map(lot => '\t' + lot.toString() + '\n').join('')}` + } + hasListingId(id) { + return this.lots.findIndex((lot) => lot.listingId === id) !== -1; + } +} \ No newline at end of file diff --git a/LotListing.js b/LotListing.js index 14dab78..c23049b 100644 --- a/LotListing.js +++ b/LotListing.js @@ -12,6 +12,9 @@ module.exports = class { this.sellerUsername = listing.strSellerUsername; this.sellerStoreName = listing.strStorename; this.instantCheckout = listing.instantCheckout; + this.legoPartNumber = null; + this.blPartNumber = null; + this.calculatedPrice = 0 this.prices = [ { quantity: 1, @@ -44,7 +47,6 @@ module.exports = class { getPrice(quantity) { for(var i = this.prices.length - 1;i >= 0;i--){ const price = this.prices[i]; - console.log(price, quantity, i) if(quantity >= price.quantity){ return price.value * quantity @@ -52,6 +54,9 @@ module.exports = class { } return null; } + toString(){ + return `${this.legoPartNumber} (${this.quantity}) -${this.colorId}- @ $${this.getPrice(this.quantity)}` + } } const priceRegex = /US \$(?\d+\.\d+)/; @@ -65,6 +70,6 @@ function parseDollars(val) { if(!stringPrice){ return 999999999; } else { - return parseFloat(stringPrice); + return parseFloat(stringPrice) === 0 ? 0.01 : parseFloat(stringPrice); } } \ No newline at end of file diff --git a/bricklink.js b/bricklink.js index 1109dbd..84619ae 100644 --- a/bricklink.js +++ b/bricklink.js @@ -1,32 +1,41 @@ const superagent = require('superagent'); const LotListing = require('./LotListing') const brickLinkAgent = superagent.agent(); +const htmlParser = require('node-html-parser'); module.exports = { getInternalPartId(legoPartNumber) { return new Promise((resolve, reject) => { brickLinkAgent.get(`https://www.bricklink.com/v2/catalog/catalogitem.page?P=${legoPartNumber}`) // .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 partIdRegex = /idItem:\s+(?\d+)/gm; const matches = partIdRegex.exec(response.text); resolve(matches.groups?.idItem) })).catch(error => { - console.error('HTTP error from internalPartId', error); reject(error); }) }) }, - getListings(internalPartId) { - return new Promise((resolve, reject) => { - brickLinkAgent.get(`https://www.bricklink.com/ajax/clone/catalogifs.ajax`) - .query({ + getListings(internalPartId, colorId) { + return new Promise(async (resolve, reject) => { + await new Promise((resolve) => setTimeout(resolve, Math.random() * 500 + 100)); + var query = { itemid: internalPartId, - loc: 'US' - }) + loc: 'US', + rpp: 100 + // cond: 'U' + } + if(colorId){ + query.color = colorId + } + brickLinkAgent.get(`https://www.bricklink.com/ajax/clone/catalogifs.ajax`) + .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.list) { + console.log(response.body, query) return reject('no list found'); } resolve(response.body.list.map(l => new LotListing(l))); @@ -37,6 +46,14 @@ module.exports = { }) }, + 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 Promise((resolve, reject) => { brickLinkAgent.get(`https://store.bricklink.com/${sellerUsername}`) @@ -49,6 +66,40 @@ module.exports = { reject(error); }) + }) + }, + getSetInventory(setId) { + return new Promise((resolve, reject) => { + brickLinkAgent.get(`https://www.bricklink.com/catalogItemInv.asp`) + .query({ + S: setId + }) + .then((response => { + var root = htmlParser.parse(response.text); + + const rows = root.querySelectorAll('.IV_ITEM') + const parsed = rows.map(row => { + const partRegex = /\/v2\/catalog\/catalogitem.page\?P=(?.+)&idColor=(?\d+)/gm; + const quantityRegex = /\ (?\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); + }) + }) } } diff --git a/colors.js b/colors.js new file mode 100644 index 0000000..eec4a87 --- /dev/null +++ b/colors.js @@ -0,0 +1,203 @@ +var _varColorList = []; +_varColorList.push( { idColor: 0, strColorName: '(Not Applicable)', group: 0, rgb: '' } ); +_varColorList.push( { idColor: 41, strColorName: 'Aqua', group: 1, rgb: 'BCE5DC' } ); +_varColorList.push( { idColor: 11, strColorName: 'Black', group: 1, rgb: '212121' } ); +_varColorList.push( { idColor: 7, strColorName: 'Blue', group: 1, rgb: '0057A6' } ); +_varColorList.push( { idColor: 97, strColorName: 'Blue-Violet', group: 1, rgb: '506CEF' } ); +_varColorList.push( { idColor: 36, strColorName: 'Bright Green', group: 1, rgb: '10CB31' } ); +_varColorList.push( { idColor: 105, strColorName: 'Bright Light Blue', group: 1, rgb: 'BCD1ED' } ); +_varColorList.push( { idColor: 110, strColorName: 'Bright Light Orange', group: 1, rgb: 'FFC700' } ); +_varColorList.push( { idColor: 103, strColorName: 'Bright Light Yellow', group: 1, rgb: 'FFF08C' } ); +_varColorList.push( { idColor: 104, strColorName: 'Bright Pink', group: 1, rgb: 'F7BCDA' } ); +_varColorList.push( { idColor: 8, strColorName: 'Brown', group: 1, rgb: '6B3F22' } ); +_varColorList.push( { idColor: 227, strColorName: 'Clikits Lavender', group: 1, rgb: 'E0AAD9' } ); +_varColorList.push( { idColor: 220, strColorName: 'Coral', group: 1, rgb: 'FF8172' } ); +_varColorList.push( { idColor: 153, strColorName: 'Dark Azure', group: 1, rgb: '009FE0' } ); +_varColorList.push( { idColor: 63, strColorName: 'Dark Blue', group: 1, rgb: '243757' } ); +_varColorList.push( { idColor: 109, strColorName: 'Dark Blue-Violet', group: 1, rgb: '2032B0' } ); +_varColorList.push( { idColor: 85, strColorName: 'Dark Bluish Gray', group: 1, rgb: '595D60' } ); +_varColorList.push( { idColor: 120, strColorName: 'Dark Brown', group: 1, rgb: '50372F' } ); +_varColorList.push( { idColor: 10, strColorName: 'Dark Gray', group: 1, rgb: '6B5A5A' } ); +_varColorList.push( { idColor: 80, strColorName: 'Dark Green', group: 1, rgb: '2E5543' } ); +_varColorList.push( { idColor: 225, strColorName: 'Dark Nougat', group: 1, rgb: 'CE7942' } ); +_varColorList.push( { idColor: 242, strColorName: 'Dark Olive Green', group: 1, rgb: '76753F' } ); +_varColorList.push( { idColor: 68, strColorName: 'Dark Orange', group: 1, rgb: 'B35408' } ); +_varColorList.push( { idColor: 47, strColorName: 'Dark Pink', group: 1, rgb: 'EF5BB3' } ); +_varColorList.push( { idColor: 89, strColorName: 'Dark Purple', group: 1, rgb: '5F2683' } ); +_varColorList.push( { idColor: 59, strColorName: 'Dark Red', group: 1, rgb: '6A0E15' } ); +_varColorList.push( { idColor: 231, strColorName: 'Dark Salmon', group: 1, rgb: 'FF6326' } ); +_varColorList.push( { idColor: 69, strColorName: 'Dark Tan', group: 1, rgb: 'B89869' } ); +_varColorList.push( { idColor: 39, strColorName: 'Dark Turquoise', group: 1, rgb: '00A29F' } ); +_varColorList.push( { idColor: 161, strColorName: 'Dark Yellow', group: 1, rgb: 'DD982E' } ); +_varColorList.push( { idColor: 29, strColorName: 'Earth Orange', group: 1, rgb: 'E6881D' } ); +_varColorList.push( { idColor: 106, strColorName: 'Fabuland Brown', group: 1, rgb: 'B3694E' } ); +_varColorList.push( { idColor: 248, strColorName: 'Fabuland Lime', group: 1, rgb: 'ADD237' } ); +_varColorList.push( { idColor: 160, strColorName: 'Fabuland Orange', group: 1, rgb: 'EF9121' } ); +_varColorList.push( { idColor: 6, strColorName: 'Green', group: 1, rgb: '00923D' } ); +_varColorList.push( { idColor: 154, strColorName: 'Lavender', group: 1, rgb: 'D3BDE3' } ); +_varColorList.push( { idColor: 152, strColorName: 'Light Aqua', group: 1, rgb: 'CFEFEA' } ); +_varColorList.push( { idColor: 62, strColorName: 'Light Blue', group: 1, rgb: 'C8D9E1' } ); +_varColorList.push( { idColor: 86, strColorName: 'Light Bluish Gray', group: 1, rgb: 'AFB5C7' } ); +_varColorList.push( { idColor: 91, strColorName: 'Light Brown', group: 1, rgb: '99663E' } ); +_varColorList.push( { idColor: 9, strColorName: 'Light Gray', group: 1, rgb: '9C9C9C' } ); +_varColorList.push( { idColor: 38, strColorName: 'Light Green', group: 1, rgb: 'D7EED1' } ); +_varColorList.push( { idColor: 246, strColorName: 'Light Lilac', group: 1, rgb: 'CDCCEE' } ); +_varColorList.push( { idColor: 35, strColorName: 'Light Lime', group: 1, rgb: 'ECEEBD' } ); +_varColorList.push( { idColor: 90, strColorName: 'Light Nougat', group: 1, rgb: 'FECCB0' } ); +_varColorList.push( { idColor: 32, strColorName: 'Light Orange', group: 1, rgb: 'FFBC36' } ); +_varColorList.push( { idColor: 56, strColorName: 'Light Pink', group: 1, rgb: 'F2D3D1' } ); +_varColorList.push( { idColor: 93, strColorName: 'Light Purple', group: 1, rgb: 'AF3195' } ); +_varColorList.push( { idColor: 26, strColorName: 'Light Salmon', group: 1, rgb: 'FCC7B7' } ); +_varColorList.push( { idColor: 40, strColorName: 'Light Turquoise', group: 1, rgb: '00C5BC' } ); +_varColorList.push( { idColor: 44, strColorName: 'Light Violet', group: 1, rgb: 'C9CAE2' } ); +_varColorList.push( { idColor: 33, strColorName: 'Light Yellow', group: 1, rgb: 'FEE89F' } ); +_varColorList.push( { idColor: 245, strColorName: 'Lilac', group: 1, rgb: '7862CE' } ); +_varColorList.push( { idColor: 34, strColorName: 'Lime', group: 1, rgb: 'C4E000' } ); +_varColorList.push( { idColor: 247, strColorName: 'Little Robots Blue', group: 1, rgb: '5DBFE4' } ); +_varColorList.push( { idColor: 72, strColorName: 'Maersk Blue', group: 1, rgb: '7DC1D8' } ); +_varColorList.push( { idColor: 71, strColorName: 'Magenta', group: 1, rgb: 'B72276' } ); +_varColorList.push( { idColor: 156, strColorName: 'Medium Azure', group: 1, rgb: '6ACEE0' } ); +_varColorList.push( { idColor: 42, strColorName: 'Medium Blue', group: 1, rgb: '82ADD8' } ); +_varColorList.push( { idColor: 240, strColorName: 'Medium Brown', group: 1, rgb: 'A16C42' } ); +_varColorList.push( { idColor: 94, strColorName: 'Medium Dark Pink', group: 1, rgb: 'F785B1' } ); +_varColorList.push( { idColor: 37, strColorName: 'Medium Green', group: 1, rgb: '91DF8C' } ); +_varColorList.push( { idColor: 157, strColorName: 'Medium Lavender', group: 1, rgb: 'C689D9' } ); +_varColorList.push( { idColor: 76, strColorName: 'Medium Lime', group: 1, rgb: 'DFE000' } ); +_varColorList.push( { idColor: 150, strColorName: 'Medium Nougat', group: 1, rgb: 'E3A05B' } ); +_varColorList.push( { idColor: 31, strColorName: 'Medium Orange', group: 1, rgb: 'FFA531' } ); +_varColorList.push( { idColor: 241, strColorName: 'Medium Tan', group: 1, rgb: 'D9C594' } ); +_varColorList.push( { idColor: 73, strColorName: 'Medium Violet', group: 1, rgb: '9391E4' } ); +_varColorList.push( { idColor: 166, strColorName: 'Neon Green', group: 1, rgb: 'DBF355' } ); +_varColorList.push( { idColor: 165, strColorName: 'Neon Orange', group: 1, rgb: 'FA5947' } ); +_varColorList.push( { idColor: 236, strColorName: 'Neon Yellow', group: 1, rgb: 'FFFC00' } ); +_varColorList.push( { idColor: 28, strColorName: 'Nougat', group: 1, rgb: 'FFAF7D' } ); +_varColorList.push( { idColor: 155, strColorName: 'Olive Green', group: 1, rgb: 'ABA953' } ); +_varColorList.push( { idColor: 4, strColorName: 'Orange', group: 1, rgb: 'FF7E14' } ); +_varColorList.push( { idColor: 23, strColorName: 'Pink', group: 1, rgb: 'F5CDD6' } ); +_varColorList.push( { idColor: 24, strColorName: 'Purple', group: 1, rgb: '7A238D' } ); +_varColorList.push( { idColor: 5, strColorName: 'Red', group: 1, rgb: 'B30006' } ); +_varColorList.push( { idColor: 88, strColorName: 'Reddish Brown', group: 1, rgb: '82422A' } ); +_varColorList.push( { idColor: 27, strColorName: 'Rust', group: 1, rgb: 'B24817' } ); +_varColorList.push( { idColor: 25, strColorName: 'Salmon', group: 1, rgb: 'FF7D5D' } ); +_varColorList.push( { idColor: 55, strColorName: 'Sand Blue', group: 1, rgb: '8899AB' } ); +_varColorList.push( { idColor: 48, strColorName: 'Sand Green', group: 1, rgb: 'A2BFA3' } ); +_varColorList.push( { idColor: 54, strColorName: 'Sand Purple', group: 1, rgb: 'B57DA5' } ); +_varColorList.push( { idColor: 58, strColorName: 'Sand Red', group: 1, rgb: 'C58D80' } ); +_varColorList.push( { idColor: 87, strColorName: 'Sky Blue', group: 1, rgb: '8AD4E1' } ); +_varColorList.push( { idColor: 2, strColorName: 'Tan', group: 1, rgb: 'EED9A4' } ); +_varColorList.push( { idColor: 99, strColorName: 'Very Light Bluish Gray', group: 1, rgb: 'E4E8E8' } ); +_varColorList.push( { idColor: 49, strColorName: 'Very Light Gray', group: 1, rgb: 'E8E8E8' } ); +_varColorList.push( { idColor: 96, strColorName: 'Very Light Orange', group: 1, rgb: 'FFDCA4' } ); +_varColorList.push( { idColor: 43, strColorName: 'Violet', group: 1, rgb: '3448A4' } ); +_varColorList.push( { idColor: 1, strColorName: 'White', group: 1, rgb: 'FFFFFF' } ); +_varColorList.push( { idColor: 3, strColorName: 'Yellow', group: 1, rgb: 'FFE001' } ); +_varColorList.push( { idColor: 158, strColorName: 'Yellowish Green', group: 1, rgb: 'E7F2A7' } ); +_varColorList.push( { idColor: 113, strColorName: 'Trans-Aqua', group: 2, rgb: 'B7C8BF' } ); +_varColorList.push( { idColor: 13, strColorName: 'Trans-Black', group: 2, rgb: '939484' } ); +_varColorList.push( { idColor: 108, strColorName: 'Trans-Bright Green', group: 2, rgb: '10Cb31' } ); +_varColorList.push( { idColor: 12, strColorName: 'Trans-Clear', group: 2, rgb: 'EEEEEE' } ); +_varColorList.push( { idColor: 14, strColorName: 'Trans-Dark Blue', group: 2, rgb: '00296B' } ); +_varColorList.push( { idColor: 50, strColorName: 'Trans-Dark Pink', group: 2, rgb: 'CE1d9b' } ); +_varColorList.push( { idColor: 20, strColorName: 'Trans-Green', group: 2, rgb: '217625' } ); +_varColorList.push( { idColor: 15, strColorName: 'Trans-Light Blue', group: 2, rgb: '68BCC5' } ); +_varColorList.push( { idColor: 226, strColorName: 'Trans-Light Bright Green', group: 2, rgb: '71EB54' } ); +_varColorList.push( { idColor: 221, strColorName: 'Trans-Light Green', group: 2, rgb: '94E5AB' } ); +_varColorList.push( { idColor: 164, strColorName: 'Trans-Light Orange', group: 2, rgb: 'E99A3A' } ); +_varColorList.push( { idColor: 114, strColorName: 'Trans-Light Purple', group: 2, rgb: 'B97AB1' } ); +_varColorList.push( { idColor: 74, strColorName: 'Trans-Medium Blue', group: 2, rgb: '76A3C8' } ); +_varColorList.push( { idColor: 234, strColorName: 'Trans-Medium Purple', group: 2, rgb: '9C41B6' } ); +_varColorList.push( { idColor: 16, strColorName: 'Trans-Neon Green', group: 2, rgb: 'C0F500' } ); +_varColorList.push( { idColor: 18, strColorName: 'Trans-Neon Orange', group: 2, rgb: 'FF4231' } ); +_varColorList.push( { idColor: 121, strColorName: 'Trans-Neon Yellow', group: 2, rgb: 'FFD700' } ); +_varColorList.push( { idColor: 98, strColorName: 'Trans-Orange', group: 2, rgb: 'E96F01' } ); +_varColorList.push( { idColor: 107, strColorName: 'Trans-Pink', group: 2, rgb: 'FF8298' } ); +_varColorList.push( { idColor: 51, strColorName: 'Trans-Purple', group: 2, rgb: '5525B7' } ); +_varColorList.push( { idColor: 17, strColorName: 'Trans-Red', group: 2, rgb: '9C0010' } ); +_varColorList.push( { idColor: 19, strColorName: 'Trans-Yellow', group: 2, rgb: 'EBF72D' } ); +_varColorList.push( { idColor: 57, strColorName: 'Chrome Antique Brass', group: 3, rgb: '645a4c' } ); +_varColorList.push( { idColor: 122, strColorName: 'Chrome Black', group: 3, rgb: '544E4F' } ); +_varColorList.push( { idColor: 52, strColorName: 'Chrome Blue', group: 3, rgb: '5C66A4' } ); +_varColorList.push( { idColor: 21, strColorName: 'Chrome Gold', group: 3, rgb: 'f1f2e1' } ); +_varColorList.push( { idColor: 64, strColorName: 'Chrome Green', group: 3, rgb: '3CB371' } ); +_varColorList.push( { idColor: 82, strColorName: 'Chrome Pink', group: 3, rgb: 'aa4d8e' } ); +_varColorList.push( { idColor: 22, strColorName: 'Chrome Silver', group: 3, rgb: 'DCDCDC' } ); +_varColorList.push( { idColor: 237, strColorName: 'Bionicle Copper', group: 4, rgb: '985750' } ); +_varColorList.push( { idColor: 238, strColorName: 'Bionicle Gold', group: 4, rgb: 'B9752F' } ); +_varColorList.push( { idColor: 239, strColorName: 'Bionicle Silver', group: 4, rgb: 'A59287' } ); +_varColorList.push( { idColor: 84, strColorName: 'Copper', group: 4, rgb: 'AC6D52' } ); +_varColorList.push( { idColor: 81, strColorName: 'Flat Dark Gold', group: 4, rgb: 'AD7118' } ); +_varColorList.push( { idColor: 95, strColorName: 'Flat Silver', group: 4, rgb: '8D949C' } ); +_varColorList.push( { idColor: 244, strColorName: 'Pearl Black', group: 4, rgb: '282725' } ); +_varColorList.push( { idColor: 77, strColorName: 'Pearl Dark Gray', group: 4, rgb: '666660' } ); +_varColorList.push( { idColor: 115, strColorName: 'Pearl Gold', group: 4, rgb: 'E79E1D' } ); +_varColorList.push( { idColor: 61, strColorName: 'Pearl Light Gold', group: 4, rgb: 'E7AE5A' } ); +_varColorList.push( { idColor: 66, strColorName: 'Pearl Light Gray', group: 4, rgb: 'ACB7C0' } ); +_varColorList.push( { idColor: 78, strColorName: 'Pearl Sand Blue', group: 4, rgb: '868FAA' } ); +_varColorList.push( { idColor: 243, strColorName: 'Pearl Sand Purple', group: 4, rgb: 'B5A1BA' } ); +_varColorList.push( { idColor: 119, strColorName: 'Pearl Very Light Gray', group: 4, rgb: 'D4D2CD' } ); +_varColorList.push( { idColor: 83, strColorName: 'Pearl White', group: 4, rgb: 'FFFFFF' } ); +_varColorList.push( { idColor: 249, strColorName: 'Reddish Copper', group: 4, rgb: 'D06926' } ); +_varColorList.push( { idColor: 235, strColorName: 'Reddish Gold', group: 4, rgb: 'E7891B' } ); +_varColorList.push( { idColor: 229, strColorName: 'Satin Trans-Black', group: 5, rgb: '939484' } ); +_varColorList.push( { idColor: 233, strColorName: 'Satin Trans-Bright Green', group: 5, rgb: '7fe15b' } ); +_varColorList.push( { idColor: 228, strColorName: 'Satin Trans-Clear', group: 5, rgb: 'FFFFFF' } ); +_varColorList.push( { idColor: 232, strColorName: 'Satin Trans-Dark Blue', group: 5, rgb: '1552e2' } ); +_varColorList.push( { idColor: 224, strColorName: 'Satin Trans-Dark Pink', group: 5, rgb: 'CE1d9b' } ); +_varColorList.push( { idColor: 223, strColorName: 'Satin Trans-Light Blue', group: 5, rgb: '68BCC5' } ); +_varColorList.push( { idColor: 230, strColorName: 'Satin Trans-Purple', group: 5, rgb: '8320B7' } ); +_varColorList.push( { idColor: 250, strColorName: 'Metallic Copper', group: 6, rgb: 'C58259' } ); +_varColorList.push( { idColor: 65, strColorName: 'Metallic Gold', group: 6, rgb: 'B8860B' } ); +_varColorList.push( { idColor: 70, strColorName: 'Metallic Green', group: 6, rgb: 'bdb573' } ); +_varColorList.push( { idColor: 67, strColorName: 'Metallic Silver', group: 6, rgb: 'C0C0C0' } ); +_varColorList.push( { idColor: 46, strColorName: 'Glow In Dark Opaque', group: 7, rgb: 'd4d5c9' } ); +_varColorList.push( { idColor: 118, strColorName: 'Glow In Dark Trans', group: 7, rgb: 'bdc6ad' } ); +_varColorList.push( { idColor: 159, strColorName: 'Glow In Dark White', group: 7, rgb: 'd9d9d9' } ); +_varColorList.push( { idColor: 60, strColorName: 'Milky White', group: 7, rgb: 'd4d3dd' } ); +_varColorList.push( { idColor: 101, strColorName: 'Glitter Trans-Clear', group: 8, rgb: 'b2adaa' } ); +_varColorList.push( { idColor: 100, strColorName: 'Glitter Trans-Dark Pink', group: 8, rgb: 'CE1d9b' } ); +_varColorList.push( { idColor: 162, strColorName: 'Glitter Trans-Light Blue', group: 8, rgb: '68BCC5' } ); +_varColorList.push( { idColor: 163, strColorName: 'Glitter Trans-Neon Green', group: 8, rgb: 'C0F500' } ); +_varColorList.push( { idColor: 222, strColorName: 'Glitter Trans-Orange', group: 8, rgb: 'D04010' } ); +_varColorList.push( { idColor: 102, strColorName: 'Glitter Trans-Purple', group: 8, rgb: '3A2B82' } ); +_varColorList.push( { idColor: 116, strColorName: 'Speckle Black-Copper', group: 9, rgb: '5F4E47' } ); +_varColorList.push( { idColor: 151, strColorName: 'Speckle Black-Gold', group: 9, rgb: 'AB9421' } ); +_varColorList.push( { idColor: 111, strColorName: 'Speckle Black-Silver', group: 9, rgb: '7C7E7C' } ); +_varColorList.push( { idColor: 117, strColorName: 'Speckle DBGray-Silver', group: 9, rgb: '4A6363' } ); +_varColorList.push( { idColor: 142, strColorName: 'Mx Aqua Green', group: 10, rgb: '27867E' } ); +_varColorList.push( { idColor: 128, strColorName: 'Mx Black', group: 10, rgb: '000000' } ); +_varColorList.push( { idColor: 132, strColorName: 'Mx Brown', group: 10, rgb: '907450' } ); +_varColorList.push( { idColor: 133, strColorName: 'Mx Buff', group: 10, rgb: 'DEC69C' } ); +_varColorList.push( { idColor: 126, strColorName: 'Mx Charcoal Gray', group: 10, rgb: '595D60' } ); +_varColorList.push( { idColor: 149, strColorName: 'Mx Clear', group: 10, rgb: 'FFFFFF' } ); +_varColorList.push( { idColor: 214, strColorName: 'Mx Foil Dark Blue', group: 10, rgb: '0057A6' } ); +_varColorList.push( { idColor: 210, strColorName: 'Mx Foil Dark Gray', group: 10, rgb: '595D60' } ); +_varColorList.push( { idColor: 212, strColorName: 'Mx Foil Dark Green', group: 10, rgb: '006400' } ); +_varColorList.push( { idColor: 215, strColorName: 'Mx Foil Light Blue', group: 10, rgb: '68AECE' } ); +_varColorList.push( { idColor: 211, strColorName: 'Mx Foil Light Gray', group: 10, rgb: '9C9C9C' } ); +_varColorList.push( { idColor: 213, strColorName: 'Mx Foil Light Green', group: 10, rgb: '7DB538' } ); +_varColorList.push( { idColor: 219, strColorName: 'Mx Foil Orange', group: 10, rgb: 'F7AD63' } ); +_varColorList.push( { idColor: 217, strColorName: 'Mx Foil Red', group: 10, rgb: '8B0000' } ); +_varColorList.push( { idColor: 216, strColorName: 'Mx Foil Violet', group: 10, rgb: '4B0082' } ); +_varColorList.push( { idColor: 218, strColorName: 'Mx Foil Yellow', group: 10, rgb: 'FED557' } ); +_varColorList.push( { idColor: 139, strColorName: 'Mx Lemon', group: 10, rgb: 'BDC618' } ); +_varColorList.push( { idColor: 124, strColorName: 'Mx Light Bluish Gray', group: 10, rgb: 'AfB5C7' } ); +_varColorList.push( { idColor: 125, strColorName: 'Mx Light Gray', group: 10, rgb: '9C9C9C' } ); +_varColorList.push( { idColor: 136, strColorName: 'Mx Light Orange', group: 10, rgb: 'F7AD63' } ); +_varColorList.push( { idColor: 137, strColorName: 'Mx Light Yellow', group: 10, rgb: 'FFE371' } ); +_varColorList.push( { idColor: 144, strColorName: 'Mx Medium Blue', group: 10, rgb: '61AFFF' } ); +_varColorList.push( { idColor: 138, strColorName: 'Mx Ochre Yellow', group: 10, rgb: 'FED557' } ); +_varColorList.push( { idColor: 140, strColorName: 'Mx Olive Green', group: 10, rgb: '7C9051' } ); +_varColorList.push( { idColor: 135, strColorName: 'Mx Orange', group: 10, rgb: 'F47B30' } ); +_varColorList.push( { idColor: 145, strColorName: 'Mx Pastel Blue', group: 10, rgb: '68AECE' } ); +_varColorList.push( { idColor: 141, strColorName: 'Mx Pastel Green', group: 10, rgb: '7DB538' } ); +_varColorList.push( { idColor: 148, strColorName: 'Mx Pink', group: 10, rgb: 'F785B1' } ); +_varColorList.push( { idColor: 130, strColorName: 'Mx Pink Red', group: 10, rgb: 'F45C40' } ); +_varColorList.push( { idColor: 129, strColorName: 'Mx Red', group: 10, rgb: 'B52C20' } ); +_varColorList.push( { idColor: 146, strColorName: 'Mx Teal Blue', group: 10, rgb: '467083' } ); +_varColorList.push( { idColor: 134, strColorName: 'Mx Terracotta', group: 10, rgb: '5C5030' } ); +_varColorList.push( { idColor: 143, strColorName: 'Mx Tile Blue', group: 10, rgb: '0057A6' } ); +_varColorList.push( { idColor: 131, strColorName: 'Mx Tile Brown', group: 10, rgb: '330000' } ); +_varColorList.push( { idColor: 127, strColorName: 'Mx Tile Gray', group: 10, rgb: '6B5A5A' } ); +_varColorList.push( { idColor: 147, strColorName: 'Mx Violet', group: 10, rgb: 'BD7D85' } ); +_varColorList.push( { idColor: 123, strColorName: 'Mx White', group: 10, rgb: 'FFFFFF' } ); +module.exports = _varColorList; \ No newline at end of file diff --git a/index.js b/index.js index 4386abf..d73de3a 100644 --- a/index.js +++ b/index.js @@ -1,100 +1,189 @@ const brickLinkAPI = require('./bricklink'); const _ = require('lodash'); -const cart = [ - { - partNumber: '4592c01', - quantity: 2 - }, - { - partNumber: '3962b', - quantity: 1 - }, - { - partNumber: '3022', - quantity: 1 - }, - { - partNumber: '4081b', - quantity: 4 - }, - { - partNumber: '4740', - quantity: 1 - }, - { - partNumber: '3031', - quantity: 1 - }, - { - partNumber: '30303', - quantity: 1 - }, - { - partNumber: '3049b', - quantity: 1 - }, - { - partNumber: '3069bpc1', - quantity: 1 - }, - { - partNumber: '3004', - quantity: 1 - }, - { - partNumber: '2342', - quantity: 1 - }, -] - Promise.each = async function(arr, fn) { // take an array and a function - const results = []; - for(const item of arr) results.push(await item); - return results; - } - - -Promise.each(cart.map(lot => { - return new Promise((resolve, reject) => { - brickLinkAPI.getInternalPartId(lot.partNumber).then((blId) => { - brickLinkAPI.getListings(blId).then(listings => { - console.log(listings.length + 'Listings found'); - const lotsWithEnoughItems = listings.filter(l => l.quantity >= lot.quantity); - // const lotsWithEnoughItemsAndAboveMinBuy = lotsWithEnoughItems.filter(lotf => lotf.minBuy === 'None' || (lotf.getPrice(lot.quantity) > lotf.minBuy)).map(lotf => { - // lotf.calculatedPrice = lotf.getPrice(lot.quantity); - // return lotf; - // }) - setTimeout(() => { - resolve(lotsWithEnoughItems); - - }, 1000) - }).catch(error => { - console.error('Failed to fetch listings', error); - reject(error); +const util = require('util'); +const Cart = require('./Cart'); + +function getRandomizedDelay(){ + return Math.random() * 500 + 500; +} + +async function getListings(order) { + const results = [] + for (var i = 0; i < order.length; i++) { + const lot = order[i]; + try { + var listings = await brickLinkAPI.getListingsByLegoPartNumber(lot.partNumber, lot.colorId); + console.log(listings.length + 'Listings found for', lot.partNumber, `(#${lot.colorId}#)`); + const lotsWithEnoughItems = listings.filter(l => l.quantity >= lot.quantity); + results.push(lotsWithEnoughItems.map(lota => { + //update the quantity to only what we need + lota.quantity = lot.quantity; + return lota; + })); + //Wait added to not cause a 403. dont want to dos bricklink + await new Promise(resolve => setTimeout(resolve, getRandomizedDelay())); + } catch (error) { + console.error('Unable to source: ', lot.partNumber, error) + } + + } + return results; +} +brickLinkAPI.getSetInventory('4990-1').then((inventory) => { + getListings(inventory).then(parts => { + var carts = []; + const possibleLots = _.flatten(parts); + + possibleLots.forEach(lot => { + const group = _.findIndex(carts, l => l.sellerUsername === lot.sellerUsername); + if (group === -1) { + const newGroup = new Cart(lot.sellerUsername, lot.minBuy, [lot]); + + carts.push(newGroup); + + } else { + carts[group].addToCart(lot); + } + }) + //sometimes sellers make multiple listings of the same part in the same color. filter for the cheapest + carts = carts.map(cart => { + const groupedLots = _.groupBy(cart.lots, (lot) => `${lot.legoPartNumber}-${lot.colorId}`); + const listingsToRemove = []; + _.keys(groupedLots).forEach((partType) => { + if (groupedLots[partType].length <= 1) return; + const pricesPerLot = groupedLots[partType].map((lot) => lot.getPrice(lot.quantity)); + const minPrice = Math.min(...pricesPerLot); + const lotsAtMinPrice = groupedLots[partType].filter((lot) => lot.getPrice(lot.quantity) === minPrice); + if (lotsAtMinPrice.length === 1) { + + listingsToRemove.push(..._.difference(groupedLots[partType], ...lotsAtMinPrice)) + return; + } + //Just keep the first one at this point. they're the same price. + listingsToRemove.push(...groupedLots[partType].slice(1)); }) - }).catch(error => { - console.error('Failed to fetch internal id', error); - reject(error); + if(listingsToRemove.length > 0) console.log(`Removing ${listingsToRemove.length} listings for duplication from seller ${cart.sellerUsername}....`); + + + return new Cart( + cart.sellerUsername, + cart.minBuy, + _.differenceBy(cart.lots, listingsToRemove, (a) => a.listingId)) }) - }); -})).then(parts => { - var groupedLots = []; - const possibleLots = _.flatten(parts); - - possibleLots.forEach(lot => { - const group = _.findIndex(groupedLots, l => l.sellerUsername === lot.sellerUsername); - if(group === -1) { - const newGroup = { - sellerUsername: lot.sellerUsername, - minBuy: lot.minBuy, - lots: [lot] - } - groupedLots.push(newGroup); - - } else { - groupedLots[group].lots.push(lot); + + + + //throw out any carts that won't meet min buy + var aboveMinBuy = carts.filter((cart) => cart.getPrice() >= cart.minBuy); + + + console.log(`Found ${aboveMinBuy.length} possible carts`); + + //brute force things here for now + //generage every possible order combo + + + + //for every cart, see if we have everything. + var currentlyUnsourcedParts = inventory; + var calculatedCarts = []; + + while (currentlyUnsourcedParts.length > 0) { + console.log(`Searching for parts to cover ${currentlyUnsourcedParts.length} lots...`, currentlyUnsourcedParts) + const bestAddition = getCartWithBestCoverage(aboveMinBuy, currentlyUnsourcedParts); + calculatedCarts.push(bestAddition); + console.log(`Added new cart to cover ${bestAddition.lots.length} lots.`, bestAddition); + + //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 groupedLots = _.groupBy(lotsFromAllCarts, (lot) => `${lot.legoPartNumber}-${lot.colorId}`); + const listingsToRemove = []; + _.keys(groupedLots).forEach((partType) => { + const legoPartNumber = partType.split('-')[0]; + const colorId = partType.split('-')[1]; + + if (groupedLots[partType].length <= 1) return; + const pricesPerLot = groupedLots[partType].map((lot) => lot.getPrice(lot.quantity)); + const minPrice = Math.min(...pricesPerLot); + const lotsAtMinPrice = groupedLots[partType].filter((lot) => lot.getPrice(lot.quantity) === minPrice); + if (lotsAtMinPrice.length === 1) { + //TODO possibly perform check to see if we're gonna push the others below min buy + listingsToRemove.push(...(_.difference(groupedLots[partType], lotsAtMinPrice).map(i => i.listingId))) + return; + } + + //everything is the same price here, lets just keep the one in the largest lot + const cartSizesPerLot = lotsAtMinPrice.map(lot => { + //what cart do I belong to? + const parentCart = calculatedCarts.find((cart) => cart.hasListingId(lot.listingId)); + return parentCart.lots.length; + }); + const largestCartSize = Math.max(...cartSizesPerLot); + const cartsAtLargestSize = calculatedCarts.filter(cart => cart.lots.length === largestCartSize); + if (cartsAtLargestSize.length === 1) { + //keep this cart, remove the item from the others. + //get the listing in this cart + const cart = cartsAtLargestSize[0]; + const keepingLot = cart.lots.find((lot) => lot.legoPartNumber == legoPartNumber && lot.colorId == colorId); + listingsToRemove.push(...(groupedLots[partType].filter(lot => lot.listingId !== keepingLot.listingId).map(i => i.listingId))) + return; + } + + //at this point, they're same price, and same cart sizes. Just pick the first one + listingsToRemove.push(...groupedLots[partType].slice(1).map(i => i.listingId)); + + + }) + + calculatedCarts.forEach(cart => { + listingsToRemove.forEach(listingId => cart.removeListingIdFromCart(listingId)) + }); + + + + + currentlyUnsourcedParts = getUnsourcedParts(calculatedCarts, currentlyUnsourcedParts); } + // 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'); + calculatedCarts.forEach((cart) => console.log(cart.toString())); }) - - console.log(groupedLots.filter(gl => gl.lots.length > 1)); }) +function getCartWithBestCoverage(carts, partsList) { + var coverages = carts.map(cart => { + const itemsNotInThisCart = partsList.filter(item => { + return _.findIndex(cart.lots, lot => lot.legoPartNumber === item.partNumber && lot.colorId === item.colorId) === -1; + }) + if (itemsNotInThisCart.length === 0) { + //ONE STORE SOLUTION FOUND, NICE! + // console.log('Coverage found: ', cart); + return 0; + } + return itemsNotInThisCart.length; + }) + if (coverages.reduce((acc, val) => acc + val, 0) === 0) { + + } + const bestCoverage = Math.min(...coverages); + const coverageOptions = carts.filter((value, index, array) => { + return coverages[index] === bestCoverage + }).sort((a, b) => a.getPrice() - b.getPrice()); + if(bestCoverage === partsList.length){ + //no carts are providing coverage. + throw new Error('Cannot fulfill order, lacking coverage'); + } + // if(Math.min(...coverages)) === ) + // const bestCoverageIndex = coverages.indexOf(); + return coverageOptions[0]; + +} +function getUnsourcedParts(carts, partsList) { + const allLotsFromAllCarts = _.uniqBy(carts.reduce((acc, cart) => { acc.push(...cart.lots); return acc; }, []), (lot) => `${lot.legoPartNumber}_${lot.colorId}`); + return partsList.filter((part) => { + return allLotsFromAllCarts.findIndex((lot) => lot.legoPartNumber === part.partNumber && lot.colorId === part.colorId) === -1 + }) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5560c56..f70d13e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -41,6 +46,23 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, + "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==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -63,6 +85,44 @@ "wrappy": "1" } }, + "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==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + }, "fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -117,6 +177,11 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, "hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -163,6 +228,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "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==", + "requires": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "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==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", diff --git a/package.json b/package.json index 2e87cec..f949b01 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "lodash": "^4.17.21", + "node-html-parser": "^6.1.4", "superagent": "^8.0.6" } }