| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 'use strict' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-09 19:26:15 +02:00
										 |  |  | const trim = require('lodash/trim') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-11 16:06:37 +01:00
										 |  |  | const _createParseJourney = require('../../parse/journey') | 
					
						
							| 
									
										
										
										
											2019-01-09 10:51:32 +01:00
										 |  |  | const _createParseLine = require('../../parse/line') | 
					
						
							| 
									
										
										
										
											2018-07-09 19:26:15 +02:00
										 |  |  | const _parseHint = require('../../parse/hint') | 
					
						
							| 
									
										
										
										
											2017-12-11 16:06:37 +01:00
										 |  |  | const _formatStation = require('../../format/station') | 
					
						
							| 
									
										
										
										
											2017-12-18 20:01:12 +01:00
										 |  |  | const {bike} = require('../../format/filters') | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-16 14:23:27 +01:00
										 |  |  | const products = require('./products') | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | const formatLoyaltyCard = require('./loyalty-cards').format | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const transformReqBody = (body) => { | 
					
						
							|  |  |  | 	body.client = {id: 'DB', v: '16040000', type: 'IPH', name: 'DB Navigator'} | 
					
						
							| 
									
										
										
										
											2019-01-09 10:51:32 +01:00
										 |  |  | 	body.ext = 'DB.R18.06.a' | 
					
						
							| 
									
										
										
										
											2018-05-24 14:06:35 +02:00
										 |  |  | 	body.ver = '1.16' | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 	body.auth = {type: 'AID', aid: 'n91dB8Z77MLdoR0K'} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return body | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const transformJourneysQuery = (query, opt) => { | 
					
						
							|  |  |  | 	const filters = query.jnyFltrL | 
					
						
							|  |  |  | 	if (opt.bike) filters.push(bike) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	query.trfReq = { | 
					
						
							| 
									
										
										
										
											2017-12-12 23:15:06 +01:00
										 |  |  | 		jnyCl: opt.firstClass === true ? 1 : 2, | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 		tvlrProf: [{ | 
					
						
							|  |  |  | 			type: 'E', | 
					
						
							|  |  |  | 			redtnCard: opt.loyaltyCard | 
					
						
							|  |  |  | 				? formatLoyaltyCard(opt.loyaltyCard) | 
					
						
							|  |  |  | 				: null | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 		cType: 'PK' | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return query | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-09 10:51:32 +01:00
										 |  |  | const createParseLine = (profile, opt, data) => { | 
					
						
							|  |  |  | 	const parseLine = _createParseLine(profile, opt, data) | 
					
						
							|  |  |  | 	const parseLineWithAdditionalName = (l) => { | 
					
						
							|  |  |  | 		const res = parseLine(l) | 
					
						
							|  |  |  | 		if (l.addName) res.additionalName = l.addName | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return parseLineWithAdditionalName | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-13 19:59:44 +02:00
										 |  |  | const createParseJourney = (profile, opt, data) => { | 
					
						
							|  |  |  | 	const parseJourney = _createParseJourney(profile, opt, data) | 
					
						
							| 
									
										
										
										
											2017-12-11 16:06:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-11 19:40:46 +01:00
										 |  |  | 	// todo: j.sotRating, j.conSubscr, j.isSotCon, j.showARSLink, k.sotCtxt
 | 
					
						
							|  |  |  | 	// todo: j.conSubscr, j.showARSLink, j.useableTime
 | 
					
						
							| 
									
										
										
										
											2017-12-11 16:06:37 +01:00
										 |  |  | 	const parseJourneyWithPrice = (j) => { | 
					
						
							|  |  |  | 		const res = parseJourney(j) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// todo: find cheapest, find discounts
 | 
					
						
							|  |  |  | 		// todo: write a parser like vbb-parse-ticket
 | 
					
						
							|  |  |  | 		// [ {
 | 
					
						
							|  |  |  | 		// 	prc: 15000,
 | 
					
						
							|  |  |  | 		// 	isFromPrice: true,
 | 
					
						
							|  |  |  | 		// 	isBookable: true,
 | 
					
						
							|  |  |  | 		// 	isUpsell: false,
 | 
					
						
							|  |  |  | 		// 	targetCtx: 'D',
 | 
					
						
							|  |  |  | 		// 	buttonText: 'To offer selection'
 | 
					
						
							|  |  |  | 		// } ]
 | 
					
						
							|  |  |  | 		res.price = {amount: null, hint: 'No pricing information available.'} | 
					
						
							|  |  |  | 		if ( | 
					
						
							|  |  |  | 			j.trfRes && | 
					
						
							|  |  |  | 			Array.isArray(j.trfRes.fareSetL) && | 
					
						
							|  |  |  | 			j.trfRes.fareSetL[0] && | 
					
						
							|  |  |  | 			Array.isArray(j.trfRes.fareSetL[0].fareL) && | 
					
						
							|  |  |  | 			j.trfRes.fareSetL[0].fareL[0] | 
					
						
							|  |  |  | 		) { | 
					
						
							|  |  |  | 			const tariff = j.trfRes.fareSetL[0].fareL[0] | 
					
						
							|  |  |  | 			if (tariff.prc >= 0) { // wat
 | 
					
						
							| 
									
										
										
										
											2018-04-19 14:55:51 +02:00
										 |  |  | 				res.price = { | 
					
						
							|  |  |  | 					amount: tariff.prc / 100, | 
					
						
							|  |  |  | 					currency: 'EUR', | 
					
						
							|  |  |  | 					hint: null | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2017-12-11 16:06:37 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return parseJourneyWithPrice | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-09 19:26:15 +02:00
										 |  |  | 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 codesByText = Object.assign(Object.create(null), { | 
					
						
							|  |  |  | 	'journey cancelled': 'journey-cancelled', // todo: German variant
 | 
					
						
							|  |  |  | 	'stop cancelled': 'stop-cancelled', // todo: change to `stopover-cancelled`, German variant
 | 
					
						
							|  |  |  | 	'signal failure': 'signal-failure', | 
					
						
							|  |  |  | 	'signalstörung': 'signal-failure', | 
					
						
							|  |  |  | 	'additional stop': 'additional-stopover', // todo: German variant
 | 
					
						
							|  |  |  | 	'platform change': 'changed platform', // todo: use dash, German variant
 | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const parseHint = (profile, h, icons) => { | 
					
						
							|  |  |  | 	if (h.type === 'A') { | 
					
						
							|  |  |  | 		const hint = hintsByCode[h.code && h.code.trim().toLowerCase()] | 
					
						
							|  |  |  | 		if (hint) { | 
					
						
							|  |  |  | 			return Object.assign({text: h.txtN}, hint) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const res = _parseHint(profile, h, icons) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:17:15 +02:00
										 |  |  | 	if (res && h.txtN) { | 
					
						
							| 
									
										
										
										
											2018-07-09 19:26:15 +02:00
										 |  |  | 		const text = trim(h.txtN.toLowerCase(), ' ()') | 
					
						
							|  |  |  | 		if (codesByText[text]) res.code = codesByText[text] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return res | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 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 dbProfile = { | 
					
						
							| 
									
										
										
										
											2017-12-11 17:21:50 +01:00
										 |  |  | 	locale: 'de-DE', | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 	timezone: 'Europe/Berlin', | 
					
						
							|  |  |  | 	endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe', | 
					
						
							| 
									
										
										
										
											2018-01-15 00:45:05 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	salt: Buffer.from('bdI8UVj40K5fvxwf', 'utf8'), | 
					
						
							|  |  |  | 	addChecksum: true, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 	transformReqBody, | 
					
						
							|  |  |  | 	transformJourneysQuery, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-16 17:00:06 +01:00
										 |  |  | 	products: products, | 
					
						
							| 
									
										
										
										
											2017-12-12 03:28:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 	// todo: parseLocation
 | 
					
						
							| 
									
										
										
										
											2017-12-11 16:06:37 +01:00
										 |  |  | 	parseJourney: createParseJourney, | 
					
						
							| 
									
										
										
										
											2019-01-09 10:51:32 +01:00
										 |  |  | 	parseLine: createParseLine, | 
					
						
							| 
									
										
										
										
											2018-07-09 19:26:15 +02:00
										 |  |  | 	parseHint, | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 14:55:51 +02:00
										 |  |  | 	formatStation, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-26 18:35:27 +02:00
										 |  |  | 	trip: true, // todo: #49
 | 
					
						
							|  |  |  | 	reachableFrom: true | 
					
						
							| 
									
										
										
										
											2017-11-12 23:51:39 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = dbProfile |