@bytebrand/export-cars 1.23.3 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exporters/fb.exporter.js +4 -0
- package/dist/exporters/fb.exporter.js.map +1 -1
- package/dist/formats/fb-xml.builder.js +7 -0
- package/dist/formats/fb-xml.builder.js.map +1 -1
- package/dist/services/images.service.js +1 -1
- package/dist/services/images.service.js.map +1 -1
- package/dist/transformers/fb.transformer.d.ts +1 -0
- package/dist/transformers/fb.transformer.js +23 -25
- package/dist/transformers/fb.transformer.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/exporters/fb.exporter.ts +5 -0
- package/src/formats/fb-xml.builder.ts +9 -0
- package/src/services/images.service.ts +1 -1
- package/src/transformers/fb.transformer.ts +40 -27
package/package.json
CHANGED
|
@@ -41,6 +41,11 @@ export class FbExporter implements Exporter {
|
|
|
41
41
|
const car = await fbTransformer.transform({ owner, ...data });
|
|
42
42
|
console.timeEnd(`Transform ${data.car._id}`);
|
|
43
43
|
|
|
44
|
+
if (!car) {
|
|
45
|
+
console.log(`Skip car ${data.car._id} because it has no real images`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
await fbXmlBuilder.addItem(car as FbXmlItem);
|
|
45
50
|
this.carService.counter.countCar(data.images);
|
|
46
51
|
}
|
|
@@ -19,6 +19,15 @@ export class FbXmlBuilder extends XMLBuilderBase {
|
|
|
19
19
|
if (key === 'additional_image_link' && Array.isArray(data)) {
|
|
20
20
|
return data.map((value: string) => this.getElement(key, value, true)).join('');
|
|
21
21
|
}
|
|
22
|
+
if (key === 'vehicle_specifications' && Array.isArray(data)) {
|
|
23
|
+
// getElement() treats falsy values as "missing" — stringify so a legitimate 0
|
|
24
|
+
// (e.g. CO2_emissions for an EV) still renders instead of an empty <value>.
|
|
25
|
+
const stringified = data.map((spec: { type: string; units: string; value: number }) => ({
|
|
26
|
+
...spec,
|
|
27
|
+
value: String(spec.value),
|
|
28
|
+
}));
|
|
29
|
+
return this.getElementsArray(key, stringified);
|
|
30
|
+
}
|
|
22
31
|
if (Array.isArray(data)) {
|
|
23
32
|
return this.getElementsArray(key, data);
|
|
24
33
|
}
|
|
@@ -202,7 +202,7 @@ export class ImagesService {
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
if (!ignoreDefImages && imageWithUrls.length < FB_MAX_IMAGE_COUNT_FOR_DEAFULT) {
|
|
205
|
-
|
|
205
|
+
this.DEFAULT_IMAGES.forEach(url => imageWithUrls.push({ url }));
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
return imageWithUrls.map(i => i.url);
|
|
@@ -44,6 +44,9 @@ import {
|
|
|
44
44
|
TIMESTAMP_FIRST_YEAR,
|
|
45
45
|
} from '../constants/fb.constants';
|
|
46
46
|
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
48
|
+
const vinGenerator = require('vin-generator');
|
|
49
|
+
|
|
47
50
|
@Injectable()
|
|
48
51
|
export class FbTransformer {
|
|
49
52
|
private translator: TranslatorExtended;
|
|
@@ -71,6 +74,13 @@ export class FbTransformer {
|
|
|
71
74
|
images: ImboImageResponseInterface[];
|
|
72
75
|
owner: unknown;
|
|
73
76
|
}): Promise<unknown> {
|
|
77
|
+
// ignoreDefImages=true: cars with no real photos must be excluded, not padded with
|
|
78
|
+
// placeholders (matches facebook-shop.transformer.ts's AMA feed policy).
|
|
79
|
+
const image_links = await this.imagesService.getImages(car, images, ImageType.marketing, true);
|
|
80
|
+
if (!image_links.length) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
const autodeId = toString(get(car, '_id', ''));
|
|
75
85
|
const year = this.getYear(car);
|
|
76
86
|
const ownerId = get(car, 'metaData.ownerId', null);
|
|
@@ -78,9 +88,6 @@ export class FbTransformer {
|
|
|
78
88
|
// console.time(`Address ${car._id}`);
|
|
79
89
|
const address = await this.getAddress(car, owner);
|
|
80
90
|
// console.timeEnd(`Address ${car._id}`);
|
|
81
|
-
// console.time(`Images ${car._id}`);
|
|
82
|
-
const image_links = await this.imagesService.getImages(car, images, ImageType.marketing, false);
|
|
83
|
-
// console.timeEnd(`Images ${car._id}`);
|
|
84
91
|
|
|
85
92
|
return {
|
|
86
93
|
fb_page_id: '176306386132',
|
|
@@ -116,31 +123,24 @@ export class FbTransformer {
|
|
|
116
123
|
interior_color: this.getInteriorColor(car),
|
|
117
124
|
condition: this.mapCondition(car),
|
|
118
125
|
energy_efficiency_class_eu: this.getEnergyEfficiencyClass(car),
|
|
119
|
-
vehicle_specifications:
|
|
120
|
-
{
|
|
121
|
-
type: 'gas_mileage_combined',
|
|
122
|
-
units: 'l/100km',
|
|
123
|
-
value: get(car, 'consumption.wltpCombined', ''),
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
type: 'gas_mileage_city',
|
|
127
|
-
units: 'l/100km',
|
|
128
|
-
value: get(car, 'consumption.wltpSlow', ''),
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
type: 'gas_mileage_highway',
|
|
132
|
-
units: 'l/100km',
|
|
133
|
-
value: get(car, 'consumption.wltpVeryFast', ''),
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
type: 'CO2_emissions',
|
|
137
|
-
units: 'g/km',
|
|
138
|
-
value: get(car, 'environmentEmissions.wltpCo2', ''),
|
|
139
|
-
},
|
|
140
|
-
],
|
|
126
|
+
vehicle_specifications: this.getVehicleSpecifications(car),
|
|
141
127
|
};
|
|
142
128
|
}
|
|
143
129
|
|
|
130
|
+
// wltp* fields are @IsOptional on the schema — genuinely absent for a meaningful share of
|
|
131
|
+
// cars (older listings, incomplete imports). Omit the whole entry rather than emit a spec
|
|
132
|
+
// with an empty <value>, but keep legitimate 0 (e.g. CO2 for an EV) — isPresent, not truthy.
|
|
133
|
+
private getVehicleSpecifications(car: DataSchemes.Car): { type: string; units: string; value: number }[] {
|
|
134
|
+
const isPresent = (value: unknown): value is number => value !== null && value !== undefined && value !== '';
|
|
135
|
+
|
|
136
|
+
return [
|
|
137
|
+
{ type: 'gas_mileage_combined', units: 'l/100km', value: get(car, 'consumption.wltpCombined') },
|
|
138
|
+
{ type: 'gas_mileage_city', units: 'l/100km', value: get(car, 'consumption.wltpSlow') },
|
|
139
|
+
{ type: 'gas_mileage_highway', units: 'l/100km', value: get(car, 'consumption.wltpVeryFast') },
|
|
140
|
+
{ type: 'CO2_emissions', units: 'g/km', value: get(car, 'environmentEmissions.wltpCo2') },
|
|
141
|
+
].filter(spec => isPresent(spec.value));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
144
|
private getYear(car: DataSchemes.Car): number {
|
|
145
145
|
const firstModelsProductionDate = get(car, 'mainData.firstModelsProductionDate', 0) as number;
|
|
146
146
|
const firstModelsProductionYear = moment(firstModelsProductionDate * 1000).format('YYYY');
|
|
@@ -225,6 +225,13 @@ export class FbTransformer {
|
|
|
225
225
|
vin = get(car, 'marketing.fakeVIN', '');
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Meta requires a vin on every listing. Real + fake VIN are both absent for a small
|
|
229
|
+
// share of cars (marketing.fakeVIN is populated by a different upstream service) —
|
|
230
|
+
// same generated-VIN fallback already used by mobile.transformer.ts/santander.transformer.ts.
|
|
231
|
+
if (!isValidVin(vin)) {
|
|
232
|
+
vin = vinGenerator.generateVin();
|
|
233
|
+
}
|
|
234
|
+
|
|
228
235
|
return vin;
|
|
229
236
|
}
|
|
230
237
|
|
|
@@ -444,7 +451,7 @@ export class FbTransformer {
|
|
|
444
451
|
{ key: 'assistants.laneAssistant', type: 'lane_keep_assist', value: 'Spurhalte-Assistent' },
|
|
445
452
|
{ key: 'lights.ledHeadlights', type: 'led_headlights', value: 'LED Scheinwerfer' },
|
|
446
453
|
{ key: 'window.sunroof', type: 'moon_roof', value: 'Schiebedach' },
|
|
447
|
-
{ key: 'multimedia.mp3Interface', type: 'mp3
|
|
454
|
+
{ key: 'multimedia.mp3Interface', type: 'mp3', value: 'Schnittstelle' },
|
|
448
455
|
{ key: 'multimedia.navigationSystem', type: 'navigation', value: 'Navigationssystem' },
|
|
449
456
|
{ key: 'assistants.nightVisionAssistant', type: 'night_view', value: 'Nachsicht Assistent' },
|
|
450
457
|
{ key: 'window.panoramicGlassRoof', type: 'panorama_roof', value: 'Panoramaschiebedach' },
|
|
@@ -670,6 +677,12 @@ export class FbTransformer {
|
|
|
670
677
|
}
|
|
671
678
|
}
|
|
672
679
|
|
|
680
|
+
// Upper-bounded so a raw unix timestamp leaking through a non-date field (e.g.
|
|
681
|
+
// mainData.constructionYear stored as an epoch instead of a 4-digit year, seen in prod) can't
|
|
682
|
+
// be mistaken for a valid year — a timestamp is astronomically larger than any real car year.
|
|
673
683
|
function getFirstValidYear(...years: string[]): number {
|
|
674
|
-
|
|
684
|
+
const currentYear = toSafeInteger(moment().format('YYYY'));
|
|
685
|
+
return years
|
|
686
|
+
.map((year: string) => toSafeInteger(year))
|
|
687
|
+
.filter((year: number) => year > TIMESTAMP_FIRST_YEAR && year <= currentYear + 1)[0];
|
|
675
688
|
}
|