mirror of
				https://github.com/public-transport/db-vendo-client.git
				synced 2025-11-04 01:56:33 +02:00 
			
		
		
		
	refactoring
This commit is contained in:
		
							parent
							
								
									bc56d41fbe
								
							
						
					
					
						commit
						ec723b3414
					
				
					 30 changed files with 436 additions and 769 deletions
				
			
		| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
const bike = {type: 'BC', mode: 'INC'};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const accessibility = {
 | 
					 | 
				
			||||||
	none: {type: 'META', mode: 'INC', meta: 'notBarrierfree'},
 | 
					 | 
				
			||||||
	partial: {type: 'META', mode: 'INC', meta: 'limitedBarrierfree'},
 | 
					 | 
				
			||||||
	complete: {type: 'META', mode: 'INC', meta: 'completeBarrierfree'},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	bike,
 | 
					 | 
				
			||||||
	accessibility,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
const formatLinesReq = (ctx, query) => {
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		meth: 'LineMatch',
 | 
					 | 
				
			||||||
		req: {
 | 
					 | 
				
			||||||
			input: query,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	formatLinesReq,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
const formatRectangle = (profile, north, west, south, east) => {
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		llCrd: {
 | 
					 | 
				
			||||||
			x: profile.formatCoord(west),
 | 
					 | 
				
			||||||
			y: profile.formatCoord(south),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		urCrd: {
 | 
					 | 
				
			||||||
			x: profile.formatCoord(east),
 | 
					 | 
				
			||||||
			y: profile.formatCoord(north),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	formatRectangle,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
const formatRefreshJourneyReq = (ctx, refreshToken) => {
 | 
					 | 
				
			||||||
	const {profile, opt} = ctx;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const req = {
 | 
					 | 
				
			||||||
		getIST: true, // todo: make an option
 | 
					 | 
				
			||||||
		getPasslist: Boolean(opt.stopovers),
 | 
					 | 
				
			||||||
		getPolyline: Boolean(opt.polylines),
 | 
					 | 
				
			||||||
		getTariff: Boolean(opt.tickets),
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	if (profile.refreshJourneyUseOutReconL) {
 | 
					 | 
				
			||||||
		req.outReconL = [{ctx: refreshToken}];
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		req.ctxRecon = refreshToken;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		meth: 'Reconstruction',
 | 
					 | 
				
			||||||
		req,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	formatRefreshJourneyReq,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,38 +0,0 @@
 | 
				
			||||||
const formatRemarksReq = (ctx) => {
 | 
					 | 
				
			||||||
	const {profile, opt} = ctx;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const himFltrL = [];
 | 
					 | 
				
			||||||
	// todo: https://github.com/marudor/BahnhofsAbfahrten/blob/95fef0217d01344642dd423457473fe9b8b6056e/src/types/HAFAS/index.ts#L76-L91
 | 
					 | 
				
			||||||
	if (opt.products) {
 | 
					 | 
				
			||||||
		himFltrL.push(profile.formatProductsFilter(ctx, opt.products));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const req = {
 | 
					 | 
				
			||||||
		himFltrL,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	if (profile.remarksGetPolyline) {
 | 
					 | 
				
			||||||
		req.getPolyline = Boolean(opt.polylines);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// todo: stLoc, dirLoc
 | 
					 | 
				
			||||||
	// todo: comp, dept, onlyHimId, onlyToday
 | 
					 | 
				
			||||||
	// todo: dailyB, dailyE
 | 
					 | 
				
			||||||
	// see https://github.com/marudor/BahnhofsAbfahrten/blob/46a74957d68edc15713112df44e1a25150f5a178/src/types/HAFAS/HimSearch.ts#L3-L18
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (opt.results !== null) {
 | 
					 | 
				
			||||||
		req.maxNum = opt.results;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (opt.from !== null) {
 | 
					 | 
				
			||||||
		req.dateB = profile.formatDate(profile, opt.from);
 | 
					 | 
				
			||||||
		req.timeB = profile.formatTime(profile, opt.from);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (opt.to !== null) {
 | 
					 | 
				
			||||||
		req.dateE = profile.formatDate(profile, opt.to);
 | 
					 | 
				
			||||||
		req.timeE = profile.formatTime(profile, opt.to);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return {meth: 'HimSearch', req};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	formatRemarksReq,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,11 @@
 | 
				
			||||||
import {formatLocationIdentifier} from './location-identifier.js';
 | 
					import {formatLocationIdentifier} from './location-identifier.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isIBNR = /^\d{6,}$/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const formatStation = (id) => {
 | 
					const formatStation = (id) => {
 | 
				
			||||||
 | 
						if (!isIBNR.test(id)) {
 | 
				
			||||||
 | 
							throw new Error('station ID must be an IBNR.');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		type: 'S', // station
 | 
							type: 'S', // station
 | 
				
			||||||
		// todo: name necessary?
 | 
							// todo: name necessary?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,5 @@
 | 
				
			||||||
const formatStopReq = (ctx, stopRef) => {
 | 
					const formatStopReq = (ctx, stopRef) => {
 | 
				
			||||||
	return {
 | 
						// TODO
 | 
				
			||||||
		// todo: there's also `StationDetails`, are there differences?
 | 
					 | 
				
			||||||
		meth: 'LocDetails',
 | 
					 | 
				
			||||||
		req: {
 | 
					 | 
				
			||||||
			locL: [stopRef],
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								index.js
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								index.js
									
										
									
									
									
								
							| 
						 | 
					@ -223,7 +223,7 @@ const createClient = (profile, userAgent, opt = {}) => {
 | 
				
			||||||
		if (opt.results !== null) {
 | 
							if (opt.results !== null) {
 | 
				
			||||||
			// TODO query.numF = opt.results;
 | 
								// TODO query.numF = opt.results;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		const req = profile.transformJourneysQuery({profile, opt}, query);
 | 
							const req = profile.formatJourneysReq({profile, opt}, query);
 | 
				
			||||||
		const {res} = await profile.request({profile, opt}, userAgent, req);
 | 
							const {res} = await profile.request({profile, opt}, userAgent, req);
 | 
				
			||||||
		const ctx = {profile, opt, common, res};
 | 
							const ctx = {profile, opt, common, res};
 | 
				
			||||||
		const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
 | 
							const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,15 @@
 | 
				
			||||||
import {request} from '../lib/request.js';
 | 
					import {request} from '../lib/request.js';
 | 
				
			||||||
 | 
					import {products} from '../lib/products.js';
 | 
				
			||||||
 | 
					import {ageGroup, ageGroupFromAge, ageGroupLabel} from './age-group.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {formatStationBoardReq} from '../format/station-board-req.js';
 | 
					import {formatStationBoardReq} from '../format/station-board-req.js';
 | 
				
			||||||
import {formatLocationsReq} from '../format/locations-req.js';
 | 
					 | 
				
			||||||
import {formatStopReq} from '../format/stop-req.js';
 | 
					import {formatStopReq} from '../format/stop-req.js';
 | 
				
			||||||
import {formatTripReq} from '../format/trip-req.js';
 | 
					import {formatTripReq} from '../format/trip-req.js';
 | 
				
			||||||
import {formatRefreshJourneyReq} from '../format/refresh-journey-req.js';
 | 
					 | 
				
			||||||
import {formatRemarksReq} from '../format/remarks-req.js';
 | 
					 | 
				
			||||||
import {formatLinesReq} from '../format/lines-req.js';
 | 
					 | 
				
			||||||
import {formatNearbyReq} from '../format/nearby-req.js';
 | 
					import {formatNearbyReq} from '../format/nearby-req.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {parseDateTime} from '../parse/date-time.js';
 | 
					import {parseDateTime} from '../parse/date-time.js';
 | 
				
			||||||
import {parsePlatform} from '../parse/platform.js';
 | 
					import {parsePlatform} from '../parse/platform.js';
 | 
				
			||||||
import {parseBitmask as parseProductsBitmask} from '../parse/products-bitmask.js';
 | 
					import {parseProducts} from '../parse/products.js';
 | 
				
			||||||
import {parseWhen} from '../parse/when.js';
 | 
					import {parseWhen} from '../parse/when.js';
 | 
				
			||||||
import {parseDeparture} from '../parse/departure.js';
 | 
					import {parseDeparture} from '../parse/departure.js';
 | 
				
			||||||
import {parseArrival} from '../parse/arrival.js';
 | 
					import {parseArrival} from '../parse/arrival.js';
 | 
				
			||||||
| 
						 | 
					@ -24,18 +22,18 @@ import {parsePolyline} from '../parse/polyline.js';
 | 
				
			||||||
import {parseOperator} from '../parse/operator.js';
 | 
					import {parseOperator} from '../parse/operator.js';
 | 
				
			||||||
import {parseRemarks} from '../parse/remarks.js';
 | 
					import {parseRemarks} from '../parse/remarks.js';
 | 
				
			||||||
import {parseStopover} from '../parse/stopover.js';
 | 
					import {parseStopover} from '../parse/stopover.js';
 | 
				
			||||||
 | 
					import {parseLoadFactor, parseArrOrDepWithLoadFactor} from '../parse/load-factor.js';
 | 
				
			||||||
 | 
					import {parseHintByCode} from '../parse/hints-by-code.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {formatAddress} from '../format/address.js';
 | 
					import {formatAddress} from '../format/address.js';
 | 
				
			||||||
import {formatCoord} from '../format/coord.js';
 | 
					import {formatCoord} from '../format/coord.js';
 | 
				
			||||||
import {formatDate} from '../format/date.js';
 | 
					import {formatDate} from '../format/date.js';
 | 
				
			||||||
import {formatLocationFilter} from '../format/location-filter.js';
 | 
					 | 
				
			||||||
import {formatProductsFilter} from '../format/products-filter.js';
 | 
					import {formatProductsFilter} from '../format/products-filter.js';
 | 
				
			||||||
import {formatPoi} from '../format/poi.js';
 | 
					import {formatPoi} from '../format/poi.js';
 | 
				
			||||||
import {formatStation} from '../format/station.js';
 | 
					import {formatStation} from '../format/station.js';
 | 
				
			||||||
import {formatTime} from '../format/time.js';
 | 
					import {formatTime} from '../format/time.js';
 | 
				
			||||||
import {formatLocation} from '../format/location.js';
 | 
					import {formatLocation} from '../format/location.js';
 | 
				
			||||||
import {formatRectangle} from '../format/rectangle.js';
 | 
					import {formatLoyaltyCard} from '../format/loyalty-cards.js';
 | 
				
			||||||
import * as filters from '../format/filters.js';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEBUG = (/(^|,)hafas-client(,|$)/).test(process.env.DEBUG || '');
 | 
					const DEBUG = (/(^|,)hafas-client(,|$)/).test(process.env.DEBUG || '');
 | 
				
			||||||
const logRequest = DEBUG
 | 
					const logRequest = DEBUG
 | 
				
			||||||
| 
						 | 
					@ -45,32 +43,33 @@ const logResponse = DEBUG
 | 
				
			||||||
	? (_, res, body, reqId) => console.error(body)
 | 
						? (_, res, body, reqId) => console.error(body)
 | 
				
			||||||
	: () => { };
 | 
						: () => { };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const id = (ctx, x) => x;
 | 
					const id = (_ctx, x) => x;
 | 
				
			||||||
 | 
					const notImplemented = (_ctx, _x) => {
 | 
				
			||||||
 | 
						throw new Error('NotImplemented');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultProfile = {
 | 
					const defaultProfile = {
 | 
				
			||||||
	request,
 | 
						request,
 | 
				
			||||||
 | 
						products,
 | 
				
			||||||
 | 
						ageGroup, ageGroupFromAge, ageGroupLabel,
 | 
				
			||||||
	transformReqBody: id,
 | 
						transformReqBody: id,
 | 
				
			||||||
	transformReq: id,
 | 
						transformReq: id,
 | 
				
			||||||
	salt: null,
 | 
					 | 
				
			||||||
	addChecksum: false,
 | 
					 | 
				
			||||||
	addMicMac: false,
 | 
					 | 
				
			||||||
	randomizeUserAgent: true,
 | 
						randomizeUserAgent: true,
 | 
				
			||||||
	logRequest,
 | 
						logRequest,
 | 
				
			||||||
	logResponse,
 | 
						logResponse,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	formatStationBoardReq,
 | 
						formatStationBoardReq,
 | 
				
			||||||
	formatLocationsReq,
 | 
						formatLocationsReq: notImplemented,
 | 
				
			||||||
	formatStopReq,
 | 
						formatStopReq,
 | 
				
			||||||
	formatTripReq,
 | 
						formatTripReq,
 | 
				
			||||||
	formatRefreshJourneyReq,
 | 
					 | 
				
			||||||
	formatRemarksReq,
 | 
					 | 
				
			||||||
	formatLinesReq,
 | 
					 | 
				
			||||||
	formatNearbyReq,
 | 
						formatNearbyReq,
 | 
				
			||||||
 | 
						formatJourneysReq: notImplemented,
 | 
				
			||||||
 | 
						formatRefreshJourneyReq: notImplemented,
 | 
				
			||||||
	transformJourneysQuery: id,
 | 
						transformJourneysQuery: id,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	parseDateTime,
 | 
						parseDateTime,
 | 
				
			||||||
	parsePlatform,
 | 
						parsePlatform,
 | 
				
			||||||
	parseProductsBitmask,
 | 
						parseProducts,
 | 
				
			||||||
	parseWhen,
 | 
						parseWhen,
 | 
				
			||||||
	parseDeparture,
 | 
						parseDeparture,
 | 
				
			||||||
	parseArrival,
 | 
						parseArrival,
 | 
				
			||||||
| 
						 | 
					@ -78,42 +77,41 @@ const defaultProfile = {
 | 
				
			||||||
	parseJourneyLeg,
 | 
						parseJourneyLeg,
 | 
				
			||||||
	parseJourney,
 | 
						parseJourney,
 | 
				
			||||||
	parseLine,
 | 
						parseLine,
 | 
				
			||||||
	parseStationName: (_, name) => name,
 | 
						parseStationName: id,
 | 
				
			||||||
	parseLocation,
 | 
						parseLocation,
 | 
				
			||||||
	parsePolyline,
 | 
						parsePolyline,
 | 
				
			||||||
	parseOperator,
 | 
						parseOperator,
 | 
				
			||||||
	parseRemarks,
 | 
						parseRemarks,
 | 
				
			||||||
	parseStopover,
 | 
						parseStopover,
 | 
				
			||||||
 | 
						parseLoadFactor,
 | 
				
			||||||
 | 
						parseArrOrDepWithLoadFactor,
 | 
				
			||||||
 | 
						parseHintByCode,
 | 
				
			||||||
 | 
						parsePrice: notImplemented,
 | 
				
			||||||
 | 
						parseTickets: notImplemented,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	formatAddress,
 | 
						formatAddress,
 | 
				
			||||||
	formatCoord,
 | 
						formatCoord,
 | 
				
			||||||
	formatDate,
 | 
						formatDate,
 | 
				
			||||||
	formatLocationFilter,
 | 
						formatLocation,
 | 
				
			||||||
	formatProductsFilter,
 | 
						formatLocationFilter: notImplemented,
 | 
				
			||||||
 | 
						formatLoyaltyCard,
 | 
				
			||||||
	formatPoi,
 | 
						formatPoi,
 | 
				
			||||||
 | 
						formatProductsFilter,
 | 
				
			||||||
	formatStation,
 | 
						formatStation,
 | 
				
			||||||
	formatTime,
 | 
						formatTime,
 | 
				
			||||||
	formatLocation,
 | 
						formatTravellers: notImplemented,
 | 
				
			||||||
	formatRectangle,
 | 
						formatRectangle: id,
 | 
				
			||||||
	filters,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	journeysOutFrwd: true, // `journeys()` method: support for `outFrwd` field?
 | 
						journeysOutFrwd: true,
 | 
				
			||||||
	// todo: https://github.com/KDE/kpublictransport/commit/c7c54304160d8f22eab0c91812a107aca82304b7
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// `departures()` method: support for `getPasslist` field?
 | 
					 | 
				
			||||||
	departuresGetPasslist: false,
 | 
						departuresGetPasslist: false,
 | 
				
			||||||
	// `departures()` method: support for `stbFltrEquiv` field?
 | 
					 | 
				
			||||||
	departuresStbFltrEquiv: false,
 | 
						departuresStbFltrEquiv: false,
 | 
				
			||||||
 | 
						trip: true,
 | 
				
			||||||
	trip: false,
 | 
					 | 
				
			||||||
	radar: false,
 | 
						radar: false,
 | 
				
			||||||
	refreshJourney: true,
 | 
						refreshJourney: true,
 | 
				
			||||||
	// refreshJourney(): use `outReconL[]` instead of `ctxRecon`?
 | 
					 | 
				
			||||||
	refreshJourneyUseOutReconL: false,
 | 
						refreshJourneyUseOutReconL: false,
 | 
				
			||||||
	tripsByName: false,
 | 
						tripsByName: false,
 | 
				
			||||||
	remarks: false,
 | 
						remarks: false,
 | 
				
			||||||
	// `remarks()` method: support for `getPolyline` field?
 | 
						remarksGetPolyline: false,
 | 
				
			||||||
	remarksGetPolyline: false, // `remarks()` method: support for `getPolyline` field?
 | 
					 | 
				
			||||||
	lines: false,
 | 
						lines: false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +0,0 @@
 | 
				
			||||||
// For any type of "thing to parse", there's >=1 parse functions.
 | 
					 | 
				
			||||||
// By composing custom parse function(s) with the default ones, one
 | 
					 | 
				
			||||||
// can customize the behaviour of hafas-client. Profiles extensively
 | 
					 | 
				
			||||||
// use this mechanism.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Each parse function has the following signature:
 | 
					 | 
				
			||||||
// ({opt, profile, common, res}, ...raw) => newParsed
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Compose a new/custom parse function with the old/existing parse
 | 
					 | 
				
			||||||
// function, so that the new fn will be called with the output of the
 | 
					 | 
				
			||||||
// old fn.
 | 
					 | 
				
			||||||
const parseHook = (oldParse, newParse) => {
 | 
					 | 
				
			||||||
	return (ctx, ...args) => {
 | 
					 | 
				
			||||||
		return newParse({
 | 
					 | 
				
			||||||
			...ctx,
 | 
					 | 
				
			||||||
			parsed: oldParse({...ctx, parsed: {}}, ...args),
 | 
					 | 
				
			||||||
		}, ...args);
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	parseHook,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,63 +0,0 @@
 | 
				
			||||||
const findById = (needle) => {
 | 
					 | 
				
			||||||
	const needleStopId = needle.id;
 | 
					 | 
				
			||||||
	const needleStationId = needle.station
 | 
					 | 
				
			||||||
		? needle.station.id
 | 
					 | 
				
			||||||
		: null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (stop) => {
 | 
					 | 
				
			||||||
		if (needleStopId === stop.id) {
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		const stationId = stop.station
 | 
					 | 
				
			||||||
			? stop.station.id
 | 
					 | 
				
			||||||
			: null;
 | 
					 | 
				
			||||||
		if (needleStationId && stationId && needleStationId === stationId) {
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// todo: `needleStationId === stop.id`? `needleStopId === stationId`?
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const sliceLeg = (leg, from, to) => {
 | 
					 | 
				
			||||||
	if (!Array.isArray(leg.stopovers)) {
 | 
					 | 
				
			||||||
		throw new Error('leg.stopovers must be an array.');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const stops = leg.stopovers.map(st => st.stop);
 | 
					 | 
				
			||||||
	const fromI = stops.findIndex(findById(from));
 | 
					 | 
				
			||||||
	if (fromI === -1) {
 | 
					 | 
				
			||||||
		throw new Error('from not found in stopovers');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	const fromStopover = leg.stopovers[fromI];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const toI = stops.findIndex(findById(to));
 | 
					 | 
				
			||||||
	if (toI === -1) {
 | 
					 | 
				
			||||||
		throw new Error('to not found in stopovers');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	const toStopover = leg.stopovers[toI];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (fromI === 0 && toI === leg.stopovers.length - 1) {
 | 
					 | 
				
			||||||
		return leg;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	const newLeg = Object.assign({}, leg);
 | 
					 | 
				
			||||||
	newLeg.stopovers = leg.stopovers.slice(fromI, toI + 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	newLeg.origin = fromStopover.stop;
 | 
					 | 
				
			||||||
	newLeg.departure = fromStopover.departure;
 | 
					 | 
				
			||||||
	newLeg.departureDelay = fromStopover.departureDelay;
 | 
					 | 
				
			||||||
	newLeg.scheduledDeparture = fromStopover.scheduledDeparture;
 | 
					 | 
				
			||||||
	newLeg.departurePlatform = fromStopover.departurePlatform;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	newLeg.destination = toStopover.stop;
 | 
					 | 
				
			||||||
	newLeg.arrival = toStopover.arrival;
 | 
					 | 
				
			||||||
	newLeg.arrivalDelay = toStopover.arrivalDelay;
 | 
					 | 
				
			||||||
	newLeg.scheduledArrival = toStopover.scheduledArrival;
 | 
					 | 
				
			||||||
	newLeg.arrivalPlatform = toStopover.arrivalPlatform;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return newLeg;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					 | 
				
			||||||
	sliceLeg,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ const types = {
 | 
				
			||||||
	formatStopReq: 'function',
 | 
						formatStopReq: 'function',
 | 
				
			||||||
	formatTripReq: 'function',
 | 
						formatTripReq: 'function',
 | 
				
			||||||
	formatRefreshJourneyReq: 'function',
 | 
						formatRefreshJourneyReq: 'function',
 | 
				
			||||||
 | 
						formatJourneysReq: 'function',
 | 
				
			||||||
	transformJourneysQuery: 'function',
 | 
						transformJourneysQuery: 'function',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	products: 'array',
 | 
						products: 'array',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										541
									
								
								p/db/index.js
									
										
									
									
									
								
							
							
						
						
									
										541
									
								
								p/db/index.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,540 +1,27 @@
 | 
				
			||||||
// todo: use import assertions once they're supported by Node.js & ESLint
 | 
					 | 
				
			||||||
// https://github.com/tc39/proposal-import-assertions
 | 
					 | 
				
			||||||
import {createRequire} from 'module';
 | 
					import {createRequire} from 'module';
 | 
				
			||||||
const require = createRequire(import.meta.url);
 | 
					const require = createRequire(import.meta.url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import uniqBy from 'lodash/uniqBy.js';
 | 
					 | 
				
			||||||
import slugg from 'slugg';
 | 
					 | 
				
			||||||
import without from 'lodash/without.js';
 | 
					 | 
				
			||||||
import {parseHook} from '../../lib/profile-hooks.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import {parseJourney as _parseJourney} from '../../parse/journey.js';
 | 
					 | 
				
			||||||
import {parseJourneyLeg as _parseJourneyLeg} from '../../parse/journey-leg.js';
 | 
					 | 
				
			||||||
import {parseLine as _parseLine} from '../../parse/line.js';
 | 
					 | 
				
			||||||
import {parseArrival as _parseArrival} from '../../parse/arrival.js';
 | 
					 | 
				
			||||||
import {parseDeparture as _parseDeparture} from '../../parse/departure.js';
 | 
					 | 
				
			||||||
import {parseLocation as _parseLocation} from '../../parse/location.js';
 | 
					 | 
				
			||||||
import {formatStation as _formatStation} from '../../format/station.js';
 | 
					 | 
				
			||||||
import {parseDateTime} from '../../parse/date-time.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const baseProfile = require('./base.json');
 | 
					const baseProfile = require('./base.json');
 | 
				
			||||||
import {products} from './products.js';
 | 
					import {products} from '../../lib/products.js';
 | 
				
			||||||
import {formatLoyaltyCard} from './loyalty-cards.js';
 | 
					import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
 | 
				
			||||||
import {ageGroup, ageGroupFromAge, ageGroupLabel} from './ageGroup.js';
 | 
					import {formatLocationFilter} from './location-filter.js';
 | 
				
			||||||
 | 
					import {formatLocationsReq} from './locations-req.js';
 | 
				
			||||||
const transformReqBody = (ctx, body) => {
 | 
					import {formatTravellers} from './travellers.js';
 | 
				
			||||||
	return body;
 | 
					import {parseTickets, parsePrice} from './tickets.js';
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const slices = (n, arr) => {
 | 
					 | 
				
			||||||
	const initialState = {slices: [], count: Infinity};
 | 
					 | 
				
			||||||
	return arr.reduce(({slices, count}, item) => {
 | 
					 | 
				
			||||||
		if (count >= n) {
 | 
					 | 
				
			||||||
			slices.push([item]);
 | 
					 | 
				
			||||||
			count = 1;
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			slices[slices.length - 1].push(item);
 | 
					 | 
				
			||||||
			count++;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return {slices, count};
 | 
					 | 
				
			||||||
	}, initialState).slices;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseGrid = (g) => {
 | 
					 | 
				
			||||||
	// todo: g.type, e.g. `S`
 | 
					 | 
				
			||||||
	// todo: respect `g.itemL[].(col|row)`?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// todo
 | 
					 | 
				
			||||||
	// parseGrid is being called by parseLocWithDetails, which is being called as
 | 
					 | 
				
			||||||
	// profile.parseLocation by profile.parseCommon, parseCommon hasn't finished
 | 
					 | 
				
			||||||
	// resolving all references yet, so we have to resolve them manually here.
 | 
					 | 
				
			||||||
	// This would be fixed if we resolve references on-the-fly or in a recursive/
 | 
					 | 
				
			||||||
	// iterative process.
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		title: g.title,
 | 
					 | 
				
			||||||
		rows: slices(g.nCols, g.itemL.map(item => Array.isArray(item.hints) && item.hints[0]
 | 
					 | 
				
			||||||
		|| Array.isArray(item.remarkRefs) && item.remarkRefs[0] && item.remarkRefs[0].hint
 | 
					 | 
				
			||||||
		|| {},
 | 
					 | 
				
			||||||
		)),
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ausstattungKeys = Object.assign(Object.create(null), {
 | 
					 | 
				
			||||||
	'3-s-zentrale': '3SZentrale',
 | 
					 | 
				
			||||||
	'parkplatze': 'parkingLots',
 | 
					 | 
				
			||||||
	'fahrrad-stellplatze': 'bicycleParkingRacks',
 | 
					 | 
				
			||||||
	'opnv-anbindung': 'localPublicTransport',
 | 
					 | 
				
			||||||
	'wc': 'toilets',
 | 
					 | 
				
			||||||
	'schliessfacher': 'lockers',
 | 
					 | 
				
			||||||
	'reisebedarf': 'travelShop',
 | 
					 | 
				
			||||||
	'stufenfreier-zugang': 'stepFreeAccess',
 | 
					 | 
				
			||||||
	'ein-umsteigehilfe': 'boardingAid',
 | 
					 | 
				
			||||||
	'taxi-am-bahnhof': 'taxis',
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
const parseAusstattungVal = (val) => {
 | 
					 | 
				
			||||||
	val = val.toLowerCase();
 | 
					 | 
				
			||||||
	return val === 'ja'
 | 
					 | 
				
			||||||
		? true
 | 
					 | 
				
			||||||
		: val === 'nein'
 | 
					 | 
				
			||||||
			? false
 | 
					 | 
				
			||||||
			: val;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseAusstattungGrid = (g) => {
 | 
					 | 
				
			||||||
	// filter duplicate hint rows
 | 
					 | 
				
			||||||
	const rows = uniqBy(g.rows, ([key, val]) => key + ':' + val);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const res = {};
 | 
					 | 
				
			||||||
	Object.defineProperty(res, 'raw', {value: rows});
 | 
					 | 
				
			||||||
	for (let [key, val] of rows) {
 | 
					 | 
				
			||||||
		key = ausstattungKeys[slugg(key)];
 | 
					 | 
				
			||||||
		if (key) {
 | 
					 | 
				
			||||||
			res[key] = parseAusstattungVal(val);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return res;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseReisezentrumÖffnungszeiten = (g) => {
 | 
					 | 
				
			||||||
	const res = {};
 | 
					 | 
				
			||||||
	for (const [dayOfWeek, val] of g.rows) {
 | 
					 | 
				
			||||||
		res[dayOfWeek] = val;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res.raw = g.rows;
 | 
					 | 
				
			||||||
	return res;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseLocWithDetails = ({parsed, common}, l) => {
 | 
					 | 
				
			||||||
	if (!parsed) {
 | 
					 | 
				
			||||||
		return parsed;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (parsed.type !== 'stop' && parsed.type !== 'station') {
 | 
					 | 
				
			||||||
		return parsed;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (Array.isArray(l.gridL)) {
 | 
					 | 
				
			||||||
		const resolveCells = grid => ({
 | 
					 | 
				
			||||||
			...grid,
 | 
					 | 
				
			||||||
			rows: grid.rows.map(row => row.map(cell => cell && cell.text)),
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		let grids = l.gridL
 | 
					 | 
				
			||||||
			.map(grid => parseGrid(grid, common))
 | 
					 | 
				
			||||||
			.map(resolveCells);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const ausstattung = grids.find(g => slugg(g.title) === 'ausstattung');
 | 
					 | 
				
			||||||
		if (ausstattung) {
 | 
					 | 
				
			||||||
			parsed.facilities = parseAusstattungGrid(ausstattung);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		const öffnungszeiten = grids.find(g => slugg(g.title) === 'offnungszeiten-reisezentrum');
 | 
					 | 
				
			||||||
		if (öffnungszeiten) {
 | 
					 | 
				
			||||||
			parsed.reisezentrumOpeningHours = parseReisezentrumÖffnungszeiten(öffnungszeiten);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		grids = without(grids, ausstattung, öffnungszeiten);
 | 
					 | 
				
			||||||
		if (grids.length > 0) {
 | 
					 | 
				
			||||||
			parsed.grids = grids;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return parsed;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://www.bahn.de/p/view/service/buchung/auslastungsinformation.shtml
 | 
					 | 
				
			||||||
const loadFactors = [];
 | 
					 | 
				
			||||||
loadFactors[1] = 'low-to-medium';
 | 
					 | 
				
			||||||
loadFactors[2] = 'high';
 | 
					 | 
				
			||||||
loadFactors[3] = 'very-high';
 | 
					 | 
				
			||||||
loadFactors[4] = 'exceptionally-high';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseLoadFactor = (opt, auslastung) => {
 | 
					 | 
				
			||||||
	if (!auslastung) {
 | 
					 | 
				
			||||||
		return null;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	const cls = opt.firstClass
 | 
					 | 
				
			||||||
		? 'KLASSE_1'
 | 
					 | 
				
			||||||
		: 'KLASSE_2';
 | 
					 | 
				
			||||||
	const load = auslastung.find(a => a.klasse === cls)?.stufe;
 | 
					 | 
				
			||||||
	return load && loadFactors[load.r] || null;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseArrOrDepWithLoadFactor = ({parsed, res, opt}, d) => {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/* const load = parseLoadFactor(opt, d);
 | 
					 | 
				
			||||||
	if (load) {
 | 
					 | 
				
			||||||
		parsed.loadFactor = load;
 | 
					 | 
				
			||||||
	}*/ // TODO
 | 
					 | 
				
			||||||
	return parsed;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const trfReq = (opt, refreshJourney) => {
 | 
					 | 
				
			||||||
	if ('age' in opt && 'ageGroup' in opt) {
 | 
					 | 
				
			||||||
		throw new TypeError(`\
 | 
					 | 
				
			||||||
opt.age and opt.ageGroup are mutually exclusive.
 | 
					 | 
				
			||||||
Pass in just opt.age, and the age group will calculated automatically.`);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const tvlrAgeGroup = 'age' in opt
 | 
					 | 
				
			||||||
		? ageGroupFromAge(opt.age)
 | 
					 | 
				
			||||||
		: opt.ageGroup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const basicCtrfReq = {
 | 
					 | 
				
			||||||
		klasse: opt.firstClass === true ? 'KLASSE_1' : 'KLASSE_2',
 | 
					 | 
				
			||||||
		// todo [breaking]: support multiple travelers
 | 
					 | 
				
			||||||
		reisende: [{
 | 
					 | 
				
			||||||
			typ: ageGroupLabel[tvlrAgeGroup || ageGroup.ADULT],
 | 
					 | 
				
			||||||
			anzahl: 1,
 | 
					 | 
				
			||||||
			alter: 'age' in opt
 | 
					 | 
				
			||||||
				? [String(opt.age)]
 | 
					 | 
				
			||||||
				: [],
 | 
					 | 
				
			||||||
			ermaessigungen: [formatLoyaltyCard(opt.loyaltyCard)],
 | 
					 | 
				
			||||||
		}],
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	return basicCtrfReq;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const transformJourneysQuery = ({profile, opt}, query) => {
 | 
					 | 
				
			||||||
	query = Object.assign(query, trfReq(opt, false));
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		endpoint: profile.journeysEndpoint,
 | 
					 | 
				
			||||||
		body: query,
 | 
					 | 
				
			||||||
		method: 'post',
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const formatRefreshJourneyReq = (ctx, refreshToken) => {
 | 
					 | 
				
			||||||
	const {profile, opt} = ctx;
 | 
					 | 
				
			||||||
	let query = {
 | 
					 | 
				
			||||||
		ctxRecon: refreshToken,
 | 
					 | 
				
			||||||
		deutschlandTicketVorhanden: false,
 | 
					 | 
				
			||||||
		nurDeutschlandTicketVerbindungen: false,
 | 
					 | 
				
			||||||
		reservierungsKontingenteVorhanden: false,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	query = Object.assign(query, trfReq(opt, true));
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		endpoint: profile.refreshJourneysEndpoint,
 | 
					 | 
				
			||||||
		body: query,
 | 
					 | 
				
			||||||
		method: 'post',
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseLineWithAdditionalName = ({parsed}, l) => {
 | 
					 | 
				
			||||||
	if (l.nameS && ['bus', 'tram', 'ferry'].includes(l.product)) {
 | 
					 | 
				
			||||||
		parsed.name = l.nameS;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (l.addName) {
 | 
					 | 
				
			||||||
		parsed.additionalName = parsed.name;
 | 
					 | 
				
			||||||
		parsed.name = l.addName;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return parsed;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mutateToAddPrice = (parsed, raw) => {
 | 
					 | 
				
			||||||
	parsed.price = null;
 | 
					 | 
				
			||||||
	if (raw.angebotsPreis?.betrag) {
 | 
					 | 
				
			||||||
		parsed.price = {
 | 
					 | 
				
			||||||
			amount: raw.angebotsPreis.betrag,
 | 
					 | 
				
			||||||
			currency: raw.angebotsPreis.waehrung,
 | 
					 | 
				
			||||||
			hint: null,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return parsed;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mutateToAddTickets = (parsed, opt, j) => {
 | 
					 | 
				
			||||||
	if (!opt.tickets) {
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (j.reiseAngebote && j.reiseAngebote.length > 0) { // if refreshJourney()
 | 
					 | 
				
			||||||
		parsed.tickets = j.reiseAngebote
 | 
					 | 
				
			||||||
			.filter(s => s.typ == 'REISEANGEBOT' && !s.angebotsbeziehungList.flatMap(b => b.referenzen)
 | 
					 | 
				
			||||||
				.find(r => r.referenzAngebotsoption == 'PFLICHT'))
 | 
					 | 
				
			||||||
			.map((s) => {
 | 
					 | 
				
			||||||
				const p = {
 | 
					 | 
				
			||||||
					name: s.name,
 | 
					 | 
				
			||||||
					priceObj: {
 | 
					 | 
				
			||||||
						amount: Math.round(s.preis?.betrag * 100),
 | 
					 | 
				
			||||||
						currency: s.preis?.waehrung,
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
					firstClass: s.klasse == 'KLASSE_1',
 | 
					 | 
				
			||||||
					partialFare: s.teilpreis,
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
				if (s.teilpreis) {
 | 
					 | 
				
			||||||
					p.addData = 'Teilpreis / partial fare';
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if (s.konditionsAnzeigen) {
 | 
					 | 
				
			||||||
					p.addDataTicketInfo = s.konditionsAnzeigen?.map(a => a.anzeigeUeberschrift)
 | 
					 | 
				
			||||||
						.join('. ');
 | 
					 | 
				
			||||||
					p.addDataTicketDetails = s.konditionsAnzeigen?.map(a => a.textLang)
 | 
					 | 
				
			||||||
						.join(' ');
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if (s.leuchtturmInfo) {
 | 
					 | 
				
			||||||
					p.addDataTravelInfo = s.leuchtturmInfo?.text;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return p;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		if (opt.generateUnreliableTicketUrls) {
 | 
					 | 
				
			||||||
			// TODO
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	} else if (j.angebotsPreis?.betrag) { // if journeys()
 | 
					 | 
				
			||||||
		parsed.tickets = [{
 | 
					 | 
				
			||||||
			name: 'from',
 | 
					 | 
				
			||||||
			priceObj: {
 | 
					 | 
				
			||||||
				amount: Math.round(j.angebotsPreis.betrag * 100),
 | 
					 | 
				
			||||||
				currency: j.angebotsPreis.waehrung,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseJourneyWithPriceAndTickets = ({parsed, opt}, raw) => {
 | 
					 | 
				
			||||||
	mutateToAddPrice(parsed, raw);
 | 
					 | 
				
			||||||
	mutateToAddTickets(parsed, opt, raw);
 | 
					 | 
				
			||||||
	return parsed;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseJourneyLegWithLoadFactor = ({parsed, res, opt}, raw) => {
 | 
					 | 
				
			||||||
	const load = parseLoadFactor(opt, raw.auslastungsmeldungen);
 | 
					 | 
				
			||||||
	if (load) {
 | 
					 | 
				
			||||||
		parsed.loadFactor = load;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return parsed;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// todo:
 | 
					 | 
				
			||||||
// [ { type: 'hint',
 | 
					 | 
				
			||||||
//     code: 'P5',
 | 
					 | 
				
			||||||
//     text: 'Es gilt ein besonderer Fahrpreis' }
 | 
					 | 
				
			||||||
const hintsByCode = Object.assign(Object.create(null), {
 | 
					 | 
				
			||||||
	fb: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'bicycle-conveyance',
 | 
					 | 
				
			||||||
		summary: 'bicycles conveyed',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	fr: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'bicycle-conveyance-reservation',
 | 
					 | 
				
			||||||
		summary: 'bicycles conveyed, subject to reservation',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	nf: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'no-bicycle-conveyance',
 | 
					 | 
				
			||||||
		summary: 'bicycles not conveyed',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	k2: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: '2nd-class-only',
 | 
					 | 
				
			||||||
		summary: '2. class only',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	eh: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'boarding-ramp',
 | 
					 | 
				
			||||||
		summary: 'vehicle-mounted boarding ramp available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ro: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'wheelchairs-space',
 | 
					 | 
				
			||||||
		summary: 'space for wheelchairs',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	oa: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'wheelchairs-space-reservation',
 | 
					 | 
				
			||||||
		summary: 'space for wheelchairs, subject to reservation',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	wv: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'wifi',
 | 
					 | 
				
			||||||
		summary: 'WiFi available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	wi: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'wifi',
 | 
					 | 
				
			||||||
		summary: 'WiFi available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	sn: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'snacks',
 | 
					 | 
				
			||||||
		summary: 'snacks available for purchase',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mb: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'snacks',
 | 
					 | 
				
			||||||
		summary: 'snacks available for purchase',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mp: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'snacks',
 | 
					 | 
				
			||||||
		summary: 'snacks available for purchase at the seat',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	bf: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'barrier-free',
 | 
					 | 
				
			||||||
		summary: 'barrier-free',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	rg: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'barrier-free-vehicle',
 | 
					 | 
				
			||||||
		summary: 'barrier-free vehicle',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	bt: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'on-board-bistro',
 | 
					 | 
				
			||||||
		summary: 'Bordbistro available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	br: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'on-board-restaurant',
 | 
					 | 
				
			||||||
		summary: 'Bordrestaurant available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ki: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'childrens-area',
 | 
					 | 
				
			||||||
		summary: 'children\'s area available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	kk: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'parents-childrens-compartment',
 | 
					 | 
				
			||||||
		summary: 'parent-and-children compartment available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	kr: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'kids-service',
 | 
					 | 
				
			||||||
		summary: 'DB Kids Service available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ls: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'power-sockets',
 | 
					 | 
				
			||||||
		summary: 'power sockets available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ev: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'replacement-service',
 | 
					 | 
				
			||||||
		summary: 'replacement service',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	kl: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'air-conditioned',
 | 
					 | 
				
			||||||
		summary: 'air-conditioned vehicle',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	r0: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'upward-escalator',
 | 
					 | 
				
			||||||
		summary: 'upward escalator',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	au: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'elevator',
 | 
					 | 
				
			||||||
		summary: 'elevator available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ck: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'komfort-checkin',
 | 
					 | 
				
			||||||
		summary: 'Komfort-Checkin available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	it: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'ice-sprinter',
 | 
					 | 
				
			||||||
		summary: 'ICE Sprinter service',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	rp: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'compulsory-reservation',
 | 
					 | 
				
			||||||
		summary: 'compulsory seat reservation',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	rm: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'optional-reservation',
 | 
					 | 
				
			||||||
		summary: 'optional seat reservation',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	scl: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'all-2nd-class-seats-reserved',
 | 
					 | 
				
			||||||
		summary: 'all 2nd class seats reserved',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	acl: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'all-seats-reserved',
 | 
					 | 
				
			||||||
		summary: 'all seats reserved',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	sk: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'oversize-luggage-forbidden',
 | 
					 | 
				
			||||||
		summary: 'oversize luggage not allowed',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	hu: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'animals-forbidden',
 | 
					 | 
				
			||||||
		summary: 'animals not allowed, except guide dogs',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ik: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'baby-cot-required',
 | 
					 | 
				
			||||||
		summary: 'baby cot/child seat required',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ee: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'on-board-entertainment',
 | 
					 | 
				
			||||||
		summary: 'on-board entertainment available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	toilet: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'toilet',
 | 
					 | 
				
			||||||
		summary: 'toilet available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	oc: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'wheelchair-accessible-toilet',
 | 
					 | 
				
			||||||
		summary: 'wheelchair-accessible toilet available',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	iz: {
 | 
					 | 
				
			||||||
		type: 'hint',
 | 
					 | 
				
			||||||
		code: 'intercity-2',
 | 
					 | 
				
			||||||
		summary: 'Intercity 2',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const parseHintByCode = (raw) => {
 | 
					 | 
				
			||||||
	const hint = hintsByCode[raw.key.trim()
 | 
					 | 
				
			||||||
		.toLowerCase()];
 | 
					 | 
				
			||||||
	if (hint) {
 | 
					 | 
				
			||||||
		return Object.assign({text: raw.value}, hint);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return null;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const isIBNR = /^\d{6,}$/;
 | 
					 | 
				
			||||||
const formatStation = (id) => {
 | 
					 | 
				
			||||||
	if (!isIBNR.test(id)) {
 | 
					 | 
				
			||||||
		throw new Error('station ID must be an IBNR.');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return _formatStation(id);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// todo: find option for absolute number of results
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const profile = {
 | 
					const profile = {
 | 
				
			||||||
	...baseProfile,
 | 
						...baseProfile,
 | 
				
			||||||
	locale: 'de-DE',
 | 
						locale: 'de-DE',
 | 
				
			||||||
	timezone: 'Europe/Berlin',
 | 
						timezone: 'Europe/Berlin',
 | 
				
			||||||
	addChecksum: true,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	transformReqBody,
 | 
						products,
 | 
				
			||||||
	transformJourneysQuery,
 | 
						formatJourneysReq,
 | 
				
			||||||
	formatRefreshJourneyReq,
 | 
						formatRefreshJourneyReq,
 | 
				
			||||||
 | 
						formatLocationsReq,
 | 
				
			||||||
	products: products,
 | 
						formatLocationFilter,
 | 
				
			||||||
 | 
						parsePrice,
 | 
				
			||||||
	parseLocation: parseHook(_parseLocation, parseLocWithDetails),
 | 
						parseTickets,
 | 
				
			||||||
	parseJourney: parseHook(_parseJourney, parseJourneyWithPriceAndTickets),
 | 
						formatTravellers,
 | 
				
			||||||
	parseJourneyLeg: parseHook(_parseJourneyLeg, parseJourneyLegWithLoadFactor),
 | 
					 | 
				
			||||||
	parseLine: parseHook(_parseLine, parseLineWithAdditionalName),
 | 
					 | 
				
			||||||
	parseArrival: parseHook(_parseArrival, parseArrOrDepWithLoadFactor),
 | 
					 | 
				
			||||||
	parseDeparture: parseHook(_parseDeparture, parseArrOrDepWithLoadFactor),
 | 
					 | 
				
			||||||
	parseDateTime,
 | 
					 | 
				
			||||||
	parseLoadFactor,
 | 
					 | 
				
			||||||
	parseHintByCode,
 | 
					 | 
				
			||||||
	formatStation,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	generateUnreliableTicketUrls: false,
 | 
						generateUnreliableTicketUrls: false,
 | 
				
			||||||
	refreshJourneyUseOutReconL: true,
 | 
						refreshJourneyUseOutReconL: true,
 | 
				
			||||||
| 
						 | 
					@ -542,7 +29,7 @@ const profile = {
 | 
				
			||||||
	journeysFromTrip: true,
 | 
						journeysFromTrip: true,
 | 
				
			||||||
	radar: true,
 | 
						radar: true,
 | 
				
			||||||
	reachableFrom: true,
 | 
						reachableFrom: true,
 | 
				
			||||||
	lines: false, // `.svcResL[0].res.lineL[]` is missing 🤔
 | 
						lines: false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										29
									
								
								p/db/journeys-req.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								p/db/journeys-req.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					const formatJourneysReq = (ctx, query) => {
 | 
				
			||||||
 | 
						query = Object.assign(query, ctx.profile.formatTravellers(ctx));
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							endpoint: ctx.profile.journeysEndpoint,
 | 
				
			||||||
 | 
							body: query,
 | 
				
			||||||
 | 
							method: 'post',
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formatRefreshJourneyReq = (ctx, refreshToken) => {
 | 
				
			||||||
 | 
						const {profile} = ctx;
 | 
				
			||||||
 | 
						let query = {
 | 
				
			||||||
 | 
							ctxRecon: refreshToken,
 | 
				
			||||||
 | 
							deutschlandTicketVorhanden: false,
 | 
				
			||||||
 | 
							nurDeutschlandTicketVerbindungen: false,
 | 
				
			||||||
 | 
							reservierungsKontingenteVorhanden: false,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						query = Object.assign(query, profile.formatTravellers(ctx));
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							endpoint: profile.refreshJourneysEndpoint,
 | 
				
			||||||
 | 
							body: query,
 | 
				
			||||||
 | 
							method: 'post',
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
						formatJourneysReq,
 | 
				
			||||||
 | 
						formatRefreshJourneyReq,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										65
									
								
								p/db/tickets.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								p/db/tickets.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parsePrice = (ctx, raw) => {
 | 
				
			||||||
 | 
						if (raw.angebotsPreis?.betrag) {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								amount: raw.angebotsPreis.betrag,
 | 
				
			||||||
 | 
								currency: raw.angebotsPreis.waehrung,
 | 
				
			||||||
 | 
								hint: null,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return undefined;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseTickets = (ctx, j) => {
 | 
				
			||||||
 | 
						if (!ctx.opt.tickets) {
 | 
				
			||||||
 | 
							return undefined;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						let tickets = undefined;
 | 
				
			||||||
 | 
						if (j.reiseAngebote && j.reiseAngebote.length > 0) { // if refreshJourney()
 | 
				
			||||||
 | 
							tickets = j.reiseAngebote
 | 
				
			||||||
 | 
								.filter(s => s.typ == 'REISEANGEBOT' && !s.angebotsbeziehungList.flatMap(b => b.referenzen)
 | 
				
			||||||
 | 
									.find(r => r.referenzAngebotsoption == 'PFLICHT'))
 | 
				
			||||||
 | 
								.map((s) => {
 | 
				
			||||||
 | 
									const p = {
 | 
				
			||||||
 | 
										name: s.name,
 | 
				
			||||||
 | 
										priceObj: {
 | 
				
			||||||
 | 
											amount: Math.round(s.preis?.betrag * 100),
 | 
				
			||||||
 | 
											currency: s.preis?.waehrung,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										firstClass: s.klasse == 'KLASSE_1',
 | 
				
			||||||
 | 
										partialFare: s.teilpreis,
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
									if (s.teilpreis) {
 | 
				
			||||||
 | 
										p.addData = 'Teilpreis / partial fare';
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (s.konditionsAnzeigen) {
 | 
				
			||||||
 | 
										p.addDataTicketInfo = s.konditionsAnzeigen?.map(a => a.anzeigeUeberschrift)
 | 
				
			||||||
 | 
											.join('. ');
 | 
				
			||||||
 | 
										p.addDataTicketDetails = s.konditionsAnzeigen?.map(a => a.textLang)
 | 
				
			||||||
 | 
											.join(' ');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (s.leuchtturmInfo) {
 | 
				
			||||||
 | 
										p.addDataTravelInfo = s.leuchtturmInfo?.text;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return p;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							if (ctx.opt.generateUnreliableTicketUrls) {
 | 
				
			||||||
 | 
								// TODO
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else if (j.angebotsPreis?.betrag) { // if journeys()
 | 
				
			||||||
 | 
							tickets = [{
 | 
				
			||||||
 | 
								name: 'from',
 | 
				
			||||||
 | 
								priceObj: {
 | 
				
			||||||
 | 
									amount: Math.round(j.angebotsPreis.betrag * 100),
 | 
				
			||||||
 | 
									currency: j.angebotsPreis.waehrung,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return tickets;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
						parsePrice,
 | 
				
			||||||
 | 
						parseTickets,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										29
									
								
								p/db/travellers.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								p/db/travellers.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					const formatTravellers = ({profile, opt}) => {
 | 
				
			||||||
 | 
						if ('age' in opt && 'ageGroup' in opt) {
 | 
				
			||||||
 | 
							throw new TypeError(`\
 | 
				
			||||||
 | 
					opt.age and opt.ageGroup are mutually exclusive.
 | 
				
			||||||
 | 
					Pass in just opt.age, and the age group will calculated automatically.`);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const tvlrAgeGroup = 'age' in opt
 | 
				
			||||||
 | 
							? profile.ageGroupFromAge(opt.age)
 | 
				
			||||||
 | 
							: opt.ageGroup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const basicCtrfReq = {
 | 
				
			||||||
 | 
							klasse: opt.firstClass === true ? 'KLASSE_1' : 'KLASSE_2',
 | 
				
			||||||
 | 
							// todo [breaking]: support multiple travelers
 | 
				
			||||||
 | 
							reisende: [{
 | 
				
			||||||
 | 
								typ: profile.ageGroupLabel[tvlrAgeGroup || profile.ageGroup.ADULT],
 | 
				
			||||||
 | 
								anzahl: 1,
 | 
				
			||||||
 | 
								alter: 'age' in opt
 | 
				
			||||||
 | 
									? [String(opt.age)]
 | 
				
			||||||
 | 
									: [],
 | 
				
			||||||
 | 
								ermaessigungen: [profile.formatLoyaltyCard(opt.loyaltyCard)],
 | 
				
			||||||
 | 
							}],
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						return basicCtrfReq;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
						formatTravellers,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,3 @@
 | 
				
			||||||
import {parseRemarks} from './remarks.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ARRIVAL = 'a';
 | 
					const ARRIVAL = 'a';
 | 
				
			||||||
const DEPARTURE = 'd';
 | 
					const DEPARTURE = 'd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +21,7 @@ const createParseArrOrDep = (prefix) => {
 | 
				
			||||||
			remarks: [],
 | 
								remarks: [],
 | 
				
			||||||
			origin: profile.parseLocation(ctx, d.transport?.origin || d.origin) || null,
 | 
								origin: profile.parseLocation(ctx, d.transport?.origin || d.origin) || null,
 | 
				
			||||||
			destination: profile.parseLocation(ctx, d.transport?.destination || d.destination) || null,
 | 
								destination: profile.parseLocation(ctx, d.transport?.destination || d.destination) || null,
 | 
				
			||||||
 | 
								// loadFactor: profile.parseArrOrDepWithLoadFactor(ctx, d)
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO pos
 | 
							// TODO pos
 | 
				
			||||||
| 
						 | 
					@ -33,7 +32,7 @@ const createParseArrOrDep = (prefix) => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (opt.remarks) {
 | 
							if (opt.remarks) {
 | 
				
			||||||
			res.remarks = parseRemarks(ctx, d);
 | 
								res.remarks = profile.parseRemarks(ctx, d);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// TODO opt.stopovers
 | 
							// TODO opt.stopovers
 | 
				
			||||||
		return res;
 | 
							return res;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										206
									
								
								parse/hints-by-code.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								parse/hints-by-code.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,206 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// todo:
 | 
				
			||||||
 | 
					// [ { type: 'hint',
 | 
				
			||||||
 | 
					//     code: 'P5',
 | 
				
			||||||
 | 
					//     text: 'Es gilt ein besonderer Fahrpreis' }
 | 
				
			||||||
 | 
					const hintsByCode = Object.assign(Object.create(null), {
 | 
				
			||||||
 | 
						fb: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'bicycle-conveyance',
 | 
				
			||||||
 | 
							summary: 'bicycles conveyed',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						fr: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'bicycle-conveyance-reservation',
 | 
				
			||||||
 | 
							summary: 'bicycles conveyed, subject to reservation',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						nf: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'no-bicycle-conveyance',
 | 
				
			||||||
 | 
							summary: 'bicycles not conveyed',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						k2: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: '2nd-class-only',
 | 
				
			||||||
 | 
							summary: '2. class only',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						eh: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'boarding-ramp',
 | 
				
			||||||
 | 
							summary: 'vehicle-mounted boarding ramp available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ro: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'wheelchairs-space',
 | 
				
			||||||
 | 
							summary: 'space for wheelchairs',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						oa: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'wheelchairs-space-reservation',
 | 
				
			||||||
 | 
							summary: 'space for wheelchairs, subject to reservation',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						wv: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'wifi',
 | 
				
			||||||
 | 
							summary: 'WiFi available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						wi: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'wifi',
 | 
				
			||||||
 | 
							summary: 'WiFi available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						sn: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'snacks',
 | 
				
			||||||
 | 
							summary: 'snacks available for purchase',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mb: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'snacks',
 | 
				
			||||||
 | 
							summary: 'snacks available for purchase',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mp: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'snacks',
 | 
				
			||||||
 | 
							summary: 'snacks available for purchase at the seat',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						bf: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'barrier-free',
 | 
				
			||||||
 | 
							summary: 'barrier-free',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						rg: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'barrier-free-vehicle',
 | 
				
			||||||
 | 
							summary: 'barrier-free vehicle',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						bt: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'on-board-bistro',
 | 
				
			||||||
 | 
							summary: 'Bordbistro available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						br: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'on-board-restaurant',
 | 
				
			||||||
 | 
							summary: 'Bordrestaurant available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ki: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'childrens-area',
 | 
				
			||||||
 | 
							summary: 'children\'s area available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						kk: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'parents-childrens-compartment',
 | 
				
			||||||
 | 
							summary: 'parent-and-children compartment available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						kr: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'kids-service',
 | 
				
			||||||
 | 
							summary: 'DB Kids Service available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ls: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'power-sockets',
 | 
				
			||||||
 | 
							summary: 'power sockets available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ev: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'replacement-service',
 | 
				
			||||||
 | 
							summary: 'replacement service',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						kl: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'air-conditioned',
 | 
				
			||||||
 | 
							summary: 'air-conditioned vehicle',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						r0: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'upward-escalator',
 | 
				
			||||||
 | 
							summary: 'upward escalator',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						au: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'elevator',
 | 
				
			||||||
 | 
							summary: 'elevator available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ck: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'komfort-checkin',
 | 
				
			||||||
 | 
							summary: 'Komfort-Checkin available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						it: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'ice-sprinter',
 | 
				
			||||||
 | 
							summary: 'ICE Sprinter service',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						rp: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'compulsory-reservation',
 | 
				
			||||||
 | 
							summary: 'compulsory seat reservation',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						rm: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'optional-reservation',
 | 
				
			||||||
 | 
							summary: 'optional seat reservation',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						scl: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'all-2nd-class-seats-reserved',
 | 
				
			||||||
 | 
							summary: 'all 2nd class seats reserved',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						acl: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'all-seats-reserved',
 | 
				
			||||||
 | 
							summary: 'all seats reserved',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						sk: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'oversize-luggage-forbidden',
 | 
				
			||||||
 | 
							summary: 'oversize luggage not allowed',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						hu: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'animals-forbidden',
 | 
				
			||||||
 | 
							summary: 'animals not allowed, except guide dogs',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ik: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'baby-cot-required',
 | 
				
			||||||
 | 
							summary: 'baby cot/child seat required',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						ee: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'on-board-entertainment',
 | 
				
			||||||
 | 
							summary: 'on-board entertainment available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						toilet: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'toilet',
 | 
				
			||||||
 | 
							summary: 'toilet available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						oc: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'wheelchair-accessible-toilet',
 | 
				
			||||||
 | 
							summary: 'wheelchair-accessible toilet available',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						iz: {
 | 
				
			||||||
 | 
							type: 'hint',
 | 
				
			||||||
 | 
							code: 'intercity-2',
 | 
				
			||||||
 | 
							summary: 'Intercity 2',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseHintByCode = (raw) => {
 | 
				
			||||||
 | 
						const hint = hintsByCode[raw.key.trim()
 | 
				
			||||||
 | 
							.toLowerCase()];
 | 
				
			||||||
 | 
						if (hint) {
 | 
				
			||||||
 | 
							return Object.assign({text: raw.value}, hint);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
						hintsByCode,
 | 
				
			||||||
 | 
						parseHintByCode,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -96,6 +96,11 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
 | 
				
			||||||
		Object.defineProperty(res, 'canceled', {value: true});
 | 
							Object.defineProperty(res, 'canceled', {value: true});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const load = profile.parseLoadFactor(opt, pt.auslastungsmeldungen);
 | 
				
			||||||
 | 
						if (load) {
 | 
				
			||||||
 | 
							res.loadFactor = load;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return res;
 | 
						return res;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,12 @@ const parseJourney = (ctx, j) => { // j = raw journey
 | 
				
			||||||
		// res.scheduledDays = profile.parseScheduledDays(ctx, j.serviceDays);
 | 
							// res.scheduledDays = profile.parseScheduledDays(ctx, j.serviceDays);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.price = profile.parsePrice(ctx, j);
 | 
				
			||||||
 | 
						const tickets = profile.parseTickets(ctx, j);
 | 
				
			||||||
 | 
						if (tickets) {
 | 
				
			||||||
 | 
							res.tickets = tickets;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return res;
 | 
						return res;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								parse/load-factor.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								parse/load-factor.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					// https://www.bahn.de/p/view/service/buchung/auslastungsinformation.shtml
 | 
				
			||||||
 | 
					const loadFactors = [];
 | 
				
			||||||
 | 
					loadFactors[1] = 'low-to-medium';
 | 
				
			||||||
 | 
					loadFactors[2] = 'high';
 | 
				
			||||||
 | 
					loadFactors[3] = 'very-high';
 | 
				
			||||||
 | 
					loadFactors[4] = 'exceptionally-high';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseLoadFactor = (opt, auslastung) => {
 | 
				
			||||||
 | 
						if (!auslastung) {
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const cls = opt.firstClass
 | 
				
			||||||
 | 
							? 'KLASSE_1'
 | 
				
			||||||
 | 
							: 'KLASSE_2';
 | 
				
			||||||
 | 
						const load = auslastung.find(a => a.klasse === cls)?.stufe;
 | 
				
			||||||
 | 
						return load && loadFactors[load.r] || null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseArrOrDepWithLoadFactor = (ctx, d) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* const load = parseLoadFactor(opt, d);
 | 
				
			||||||
 | 
						if (load) {
 | 
				
			||||||
 | 
							parsed.loadFactor = load;
 | 
				
			||||||
 | 
						}*/ // TODO
 | 
				
			||||||
 | 
						return undefined;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
						parseArrOrDepWithLoadFactor,
 | 
				
			||||||
 | 
						parseLoadFactor,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ const parseLocation = (ctx, l) => {
 | 
				
			||||||
		// TODO subStops
 | 
							// TODO subStops
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if ('products' in l) {
 | 
							if ('products' in l) {
 | 
				
			||||||
			stop.products = profile.parseProductsBitmask(ctx, l.products);
 | 
								stop.products = profile.parseProducts(ctx, l.products);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (common && common.locations && common.locations[stop.id]) {
 | 
							if (common && common.locations && common.locations[stop.id]) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
const parseBitmask = ({profile}, bitmask) => {
 | 
					const parseProducts = ({profile}, bitmask) => {
 | 
				
			||||||
	const res = {};
 | 
						const res = {};
 | 
				
			||||||
	for (let product of profile.products) {
 | 
						for (let product of profile.products) {
 | 
				
			||||||
		res[product.id] = Boolean(bitmask.find(p => p == product.vendo));
 | 
							res[product.id] = Boolean(bitmask.find(p => p == product.vendo));
 | 
				
			||||||
| 
						 | 
					@ -7,5 +7,5 @@ const parseBitmask = ({profile}, bitmask) => {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
	parseBitmask,
 | 
						parseProducts,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import tap from 'tap';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {createClient} from '../../index.js';
 | 
					import {createClient} from '../../index.js';
 | 
				
			||||||
import {profile as rawProfile} from '../../p/db/index.js';
 | 
					import {profile as rawProfile} from '../../p/db/index.js';
 | 
				
			||||||
import {data as loyaltyCards} from '../../p/db/loyalty-cards.js';
 | 
					import {data as loyaltyCards} from '../../format/loyalty-cards.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test');
 | 
					const client = createClient(rawProfile, 'public-transport/hafas-client:test');
 | 
				
			||||||
const {profile} = client;
 | 
					const {profile} = client;
 | 
				
			||||||
| 
						 | 
					@ -69,7 +69,7 @@ tap.test('formats a journeys() request correctly (DB)', (t) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// transformJourneysQuery() mutates its 2nd argument!
 | 
						// transformJourneysQuery() mutates its 2nd argument!
 | 
				
			||||||
	const query = {...berlinWienQuery0};
 | 
						const query = {...berlinWienQuery0};
 | 
				
			||||||
	const req = profile.transformJourneysQuery(ctx, query);
 | 
						const req = profile.formatJourneysReq(ctx, query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.same(req.body, {
 | 
						t.same(req.body, {
 | 
				
			||||||
		...berlinWienQuery0,
 | 
							...berlinWienQuery0,
 | 
				
			||||||
| 
						 | 
					@ -96,7 +96,7 @@ tap.test('formats a journeys() request with BC correctly (DB)', (t) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// transformJourneysQuery() mutates its 2nd argument!
 | 
						// transformJourneysQuery() mutates its 2nd argument!
 | 
				
			||||||
	const query = {...berlinWienQuery0};
 | 
						const query = {...berlinWienQuery0};
 | 
				
			||||||
	const req = profile.transformJourneysQuery(ctx, query);
 | 
						const req = profile.formatJourneysReq(ctx, query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.same(req.body, {
 | 
						t.same(req.body, {
 | 
				
			||||||
		...berlinWienQuery0,
 | 
							...berlinWienQuery0,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,11 @@
 | 
				
			||||||
import tap from 'tap';
 | 
					import tap from 'tap';
 | 
				
			||||||
import {parseLocation as parse} from '../../parse/location.js';
 | 
					import {parseLocation as parse} from '../../parse/location.js';
 | 
				
			||||||
import {parseBitmask as parseProductsBitmask} from '../../parse/products-bitmask.js';
 | 
					import {parseProducts} from '../../parse/products.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const profile = {
 | 
					const profile = {
 | 
				
			||||||
	parseLocation: parse,
 | 
						parseLocation: parse,
 | 
				
			||||||
	parseStationName: (_, name) => name.toLowerCase(),
 | 
						parseStationName: (_, name) => name.toLowerCase(),
 | 
				
			||||||
	parseProductsBitmask,
 | 
						parseProducts,
 | 
				
			||||||
	products: [{
 | 
						products: [{
 | 
				
			||||||
		id: 'nationalExpress',
 | 
							id: 'nationalExpress',
 | 
				
			||||||
		vendo: 'ICE',
 | 
							vendo: 'ICE',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue