1+ const fetch = require ( 'node-fetch' ) ;
2+ const moment = require ( 'moment' ) ;
3+ const fs = require ( 'fs' ) ;
4+
5+ const radiusMiles = 150 ;
6+
7+ function formatCurrency ( numberString ) {
8+ let result = ''
9+ for ( let i = numberString . length - 1 ; i > - 1 ; i -- ) {
10+ result = numberString . charAt ( i ) + result ;
11+ const pos = numberString . length - i ;
12+ if ( pos > 0 && pos % 3 === 0 ) result = ',' + result ;
13+ }
14+ return '$' + result ;
15+ }
16+
17+ function makeImageUrl ( urlParams , width , interior = false ) {
18+ const imageUrl = 'https://www.jeep.com/mediaserver/iris?'
19+ let pov = 'fullfronthero' ;
20+ let height = Math . trunc ( width / 6 * 4 ) ;
21+ if ( interior ) {
22+ pov = 'I01' ;
23+ height = Math . trunc ( width / 6 * 8 ) ;
24+ }
25+ const renderSettings = `&pov=${ pov } &width=${ width } &height=${ height } &bkgnd=white&resp=jpg` ;
26+ return `${ imageUrl } ${ urlParams } ${ renderSettings } ` ;
27+ }
28+
29+ async function getDealers ( zipCode ) {
30+ const url = 'https://www.jeep.com/bdlws/MDLSDealerLocator?zipCode=ZIPCODE&func=SALES&radius=RADIUSMILES&brandCode=J&resultsPerPage=999'
31+ const requestUrl = url
32+ . replace ( 'ZIPCODE' , zipCode )
33+ . replace ( 'RADIUSMILES' , radiusMiles ) ;
34+ const response = await fetch ( requestUrl ) ;
35+ const data = await response . json ( ) ;
36+ const result = data . status ;
37+ if ( ! result === 200 ) {
38+ console . error ( `Could not get dealer list for ${ zipCode } : ${ result } ` ) ;
39+ return [ ] ;
40+ }
41+ const dealers = [ ] ;
42+ for ( const dealer of data . dealer ) {
43+ let dealerAddress = dealer . dealerAddress1 ;
44+ if ( dealer . dealerAddress2 ) dealerAddress += dealer . dealerAddress2 ;
45+ dealers . push ( {
46+ code : dealer . dealerCode ,
47+ name : dealer . dealerName ,
48+ address : `${ dealerAddress } , ${ dealer . dealerCity } , ${ dealer . dealerState } ` ,
49+ cityState : `${ dealer . dealerCity } , ${ dealer . dealerState } `
50+ } )
51+ }
52+
53+ console . log ( `Obtained ${ dealers . length } dealers for ${ zipCode } ` ) ;
54+ return dealers ;
55+ }
56+
57+ function isCarValid ( car , filters ) {
58+ if ( filters . engineCodes ) {
59+ if ( filters . engineCodes . indexOf ( car . engineCode ) === - 1 ) return false ;
60+ }
61+
62+ if ( filters . transmissionCodes ) {
63+ if ( filters . transmissionCodes . indexOf ( car . transmissionCode ) === - 1 ) return false ;
64+ }
65+
66+ if ( filters . exteriorColorCodes ) {
67+ if ( filters . exteriorColorCodes . indexOf ( car . exteriorColorCode ) === - 1 ) return false ;
68+ }
69+
70+ if ( filters . options ) {
71+ for ( const option of filters . options ) {
72+ if ( Array . isArray ( option ) ) {
73+ let match = false ;
74+ for ( const optionCode of option ) {
75+ if ( car . options . indexOf ( optionCode ) > - 1 ) {
76+ match = true ;
77+ break ;
78+ }
79+ }
80+ if ( ! match ) return false ;
81+ } else {
82+ if ( car . options . indexOf ( option ) === - 1 ) return false ;
83+ }
84+ }
85+ }
86+
87+ return true ;
88+ }
89+
90+ async function getCars ( filters , zipCode , dealers ) {
91+ const url = 'https://www.jeep.com/hostd/inventory/getinventoryresults.json?func=SALES&includeIncentives=Y&matchType=X&modelYearCode=IUJ202010&pageNumber=PAGENUMBER&pageSize=PAGESIZE&radius=RADIUSMILES&sortBy=0&zip=ZIPCODE'
92+ const pageSize = 10000 ;
93+ const requestUrl = url
94+ . replace ( 'PAGENUMBER' , 1 )
95+ . replace ( 'ZIPCODE' , zipCode )
96+ . replace ( 'RADIUSMILES' , radiusMiles )
97+ . replace ( 'PAGESIZE' , pageSize ) ;
98+ // console.log(requestUrl);
99+ const response = await fetch ( requestUrl ) ;
100+ const data = await response . json ( ) ;
101+ const result = data . result . result
102+ if ( ! result === "SUCCESS" ) {
103+ console . error ( `${ result } : ${ data . result . errors . join ( ', ' ) } ` ) ;
104+ return [ ] ;
105+ }
106+ const cars = data . result . data . vehicles ;
107+ console . log ( `${ cars . length } cars found.` ) ;
108+ if ( cars . length === pageSize ) {
109+ console . warning ( 'There may be more cars available; send another request!' ) ;
110+ }
111+ if ( cars . length < data . result . data . metadata . totalCount ) {
112+ console . warning ( 'There are more cars available; send another request!' ) ;
113+ }
114+ filteredCars = [ ]
115+ for ( const car of cars ) {
116+ if ( filters && ! isCarValid ( car , filters ) ) continue ;
117+
118+ if ( car . incentives ) {
119+ console . log ( car . incentives ) ;
120+ }
121+
122+ const engine = car . engineDesc ;
123+ const tranny = car . transmissionDesc ;
124+ const model = car . vehicleDesc ;
125+ const vin = car . vin ;
126+ const interior = car . interiorFabric ;
127+ const employeePrice = car . price . employeePrice ;
128+ const totalPrice = car . price . netPrice ;
129+ const options = car . options ;
130+ let doors ;
131+ for ( const attribute of car . attributes . attributes ) {
132+ if ( attribute . typeDesc === "Doors" ) {
133+ doors = attribute . value ;
134+ }
135+ }
136+ const exteriorImageUrl = makeImageUrl ( car . extImage , 1000 , false ) ;
137+ const interiorImageUrl = makeImageUrl ( car . intImage , 1000 , true ) ;
138+ const modelYearCode = car . ccode . substring ( 0 , 9 ) ;
139+ const windowSticker = `https://www.jeep.com/hostd/windowsticker/getWindowStickerPdf.do?vin=${ vin } `
140+ let dealer = dealers . filter ( a => a . code === car . dealerCode ) ;
141+ if ( dealer . length !== 1 ) {
142+ console . error ( `${ dealer . length } dealers found for car with dealer code = ${ car . dealerCode } ` ) ;
143+ }
144+ dealer = dealer [ 0 ] ;
145+ // mine: https://www.jeep.com/new-inventory/vehicle-details.html?modelYearCode=IUJ202010&vin=1C4HJXFN2LW266150&dealerCode=60385&radius=150&matchType=X&statusCode=KZX&ref=details&variation=undefined
146+ // theirs: https://www.jeep.com/new-inventory/vehicle-details.html?modelYearCode=IUJ202010&vin=1C4HJXCG0LW205403&dealerCode=26852&radius=150&matchType=X&statusCode=KZX&ref=details&variation=undefined
147+ // My generated URLs don't load properly.
148+ const carUrl = `https://www.jeep.com/new-inventory/vehicle-details.html?modelYearCode=${ modelYearCode } &vin=${ vin } &dealerCode=${ dealer . code } &radius=${ radiusMiles } &matchType=X&statusCode=${ car . statusCode } &ref=details&variation=undefined`
149+ thisCar = {
150+ model, engine, tranny, vin, interior, employeePrice, totalPrice,
151+ options, doors, exteriorImageUrl, interiorImageUrl,
152+ windowSticker,
153+ url : carUrl ,
154+ finalPrice : employeePrice ? formatCurrency ( employeePrice ) : formatCurrency ( totalPrice ) ,
155+ msrp : formatCurrency ( totalPrice ) ,
156+ rawData : car ,
157+ dealer : dealer
158+ } ;
159+ filteredCars . push ( thisCar ) ;
160+ }
161+
162+ console . log ( `Obtained ${ filteredCars . length } cars from a total of ${ cars . length } for ${ zipCode } ` ) ;
163+ return filteredCars ;
164+ }
165+
166+ async function run ( ) {
167+ const zipCodes = [
168+ 77478 ,
169+ 94610
170+ ]
171+
172+ let promises = [ ] ;
173+ for ( const zipCode of zipCodes ) {
174+ promises . push ( getDealers ( zipCode ) ) ;
175+ }
176+
177+ const dealersByZip = await Promise . all ( promises ) ;
178+ const allDealers = [ ] ;
179+ for ( const dealers of dealersByZip ) {
180+ allDealers . push ( ...dealers ) ;
181+ }
182+
183+ const filter = {
184+ transmissionCodes : [ 'DFT' ] ,
185+ options : [
186+ [ 'HT1' , 'HT3' ] , // hard top
187+ 'ADE' , // heated seats
188+ 'ALP' , // adaptive cruise
189+ [ 'AAN' , 'AEK' ] , // CarPlay
190+ // 'AJ1', // ParkSense
191+ ] ,
192+ exteriorColorCodes : [ 'PRC' , 'PDN' , 'PBM' , 'PYV' , 'PGG' ]
193+ }
194+
195+ promises = [ ] ;
196+ for ( const zipCode of zipCodes ) {
197+ promises . push ( getCars ( filter , zipCode , allDealers ) ) ;
198+ }
199+
200+ const carsByZip = await Promise . all ( promises ) ;
201+ const allCars = [ ] ;
202+ for ( const cars of carsByZip ) {
203+ allCars . push ( ...cars ) ;
204+ }
205+
206+ allCars . sort ( function ( a , b ) {
207+ if ( a . finalPrice < b . finalPrice ) return - 1 ;
208+ if ( a . finalPrice > b . finalPrice ) return 1 ;
209+ return 0 ;
210+ } ) ;
211+
212+ const archive = `archive/jeep_${ moment ( ) . format ( 'YYYY-MM-DD_HH-mm-ss' ) } .json` ;
213+ fs . writeFileSync ( archive , JSON . stringify ( allCars , null , 2 ) , err => {
214+ console . error ( err ) ;
215+ } ) ;
216+
217+ fs . writeFileSync ( 'jeep.json' , JSON . stringify ( allCars , null , 2 ) , err => {
218+ console . error ( err ) ;
219+ } ) ;
220+
221+ return allCars ;
222+ }
223+
224+ module . exports = { run} ;
0 commit comments