mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-22 22:59:35 +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';
|
||||
|
||||
const isIBNR = /^\d{6,}$/;
|
||||
|
||||
const formatStation = (id) => {
|
||||
if (!isIBNR.test(id)) {
|
||||
throw new Error('station ID must be an IBNR.');
|
||||
}
|
||||
return {
|
||||
type: 'S', // station
|
||||
// todo: name necessary?
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
const formatStopReq = (ctx, stopRef) => {
|
||||
return {
|
||||
// todo: there's also `StationDetails`, are there differences?
|
||||
meth: 'LocDetails',
|
||||
req: {
|
||||
locL: [stopRef],
|
||||
},
|
||||
};
|
||||
// TODO
|
||||
};
|
||||
|
||||
export {
|
||||
|
|
2
index.js
2
index.js
|
@ -223,7 +223,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
if (opt.results !== null) {
|
||||
// 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 ctx = {profile, opt, common, res};
|
||||
const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
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 {formatLocationsReq} from '../format/locations-req.js';
|
||||
import {formatStopReq} from '../format/stop-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 {parseDateTime} from '../parse/date-time.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 {parseDeparture} from '../parse/departure.js';
|
||||
import {parseArrival} from '../parse/arrival.js';
|
||||
|
@ -24,53 +22,54 @@ import {parsePolyline} from '../parse/polyline.js';
|
|||
import {parseOperator} from '../parse/operator.js';
|
||||
import {parseRemarks} from '../parse/remarks.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 {formatCoord} from '../format/coord.js';
|
||||
import {formatDate} from '../format/date.js';
|
||||
import {formatLocationFilter} from '../format/location-filter.js';
|
||||
import {formatProductsFilter} from '../format/products-filter.js';
|
||||
import {formatPoi} from '../format/poi.js';
|
||||
import {formatStation} from '../format/station.js';
|
||||
import {formatTime} from '../format/time.js';
|
||||
import {formatLocation} from '../format/location.js';
|
||||
import {formatRectangle} from '../format/rectangle.js';
|
||||
import * as filters from '../format/filters.js';
|
||||
import {formatLoyaltyCard} from '../format/loyalty-cards.js';
|
||||
|
||||
const DEBUG = (/(^|,)hafas-client(,|$)/).test(process.env.DEBUG || '');
|
||||
const logRequest = DEBUG
|
||||
? (_, req, reqId) => console.error(String(req.body))
|
||||
: () => {};
|
||||
: () => { };
|
||||
const logResponse = DEBUG
|
||||
? (_, 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 = {
|
||||
request,
|
||||
products,
|
||||
ageGroup, ageGroupFromAge, ageGroupLabel,
|
||||
transformReqBody: id,
|
||||
transformReq: id,
|
||||
salt: null,
|
||||
addChecksum: false,
|
||||
addMicMac: false,
|
||||
randomizeUserAgent: true,
|
||||
logRequest,
|
||||
logResponse,
|
||||
|
||||
formatStationBoardReq,
|
||||
formatLocationsReq,
|
||||
formatLocationsReq: notImplemented,
|
||||
formatStopReq,
|
||||
formatTripReq,
|
||||
formatRefreshJourneyReq,
|
||||
formatRemarksReq,
|
||||
formatLinesReq,
|
||||
formatNearbyReq,
|
||||
formatJourneysReq: notImplemented,
|
||||
formatRefreshJourneyReq: notImplemented,
|
||||
transformJourneysQuery: id,
|
||||
|
||||
parseDateTime,
|
||||
parsePlatform,
|
||||
parseProductsBitmask,
|
||||
parseProducts,
|
||||
parseWhen,
|
||||
parseDeparture,
|
||||
parseArrival,
|
||||
|
@ -78,42 +77,41 @@ const defaultProfile = {
|
|||
parseJourneyLeg,
|
||||
parseJourney,
|
||||
parseLine,
|
||||
parseStationName: (_, name) => name,
|
||||
parseStationName: id,
|
||||
parseLocation,
|
||||
parsePolyline,
|
||||
parseOperator,
|
||||
parseRemarks,
|
||||
parseStopover,
|
||||
parseLoadFactor,
|
||||
parseArrOrDepWithLoadFactor,
|
||||
parseHintByCode,
|
||||
parsePrice: notImplemented,
|
||||
parseTickets: notImplemented,
|
||||
|
||||
formatAddress,
|
||||
formatCoord,
|
||||
formatDate,
|
||||
formatLocationFilter,
|
||||
formatProductsFilter,
|
||||
formatLocation,
|
||||
formatLocationFilter: notImplemented,
|
||||
formatLoyaltyCard,
|
||||
formatPoi,
|
||||
formatProductsFilter,
|
||||
formatStation,
|
||||
formatTime,
|
||||
formatLocation,
|
||||
formatRectangle,
|
||||
filters,
|
||||
formatTravellers: notImplemented,
|
||||
formatRectangle: id,
|
||||
|
||||
journeysOutFrwd: true, // `journeys()` method: support for `outFrwd` field?
|
||||
// todo: https://github.com/KDE/kpublictransport/commit/c7c54304160d8f22eab0c91812a107aca82304b7
|
||||
|
||||
// `departures()` method: support for `getPasslist` field?
|
||||
journeysOutFrwd: true,
|
||||
departuresGetPasslist: false,
|
||||
// `departures()` method: support for `stbFltrEquiv` field?
|
||||
departuresStbFltrEquiv: false,
|
||||
|
||||
trip: false,
|
||||
trip: true,
|
||||
radar: false,
|
||||
refreshJourney: true,
|
||||
// refreshJourney(): use `outReconL[]` instead of `ctxRecon`?
|
||||
refreshJourneyUseOutReconL: false,
|
||||
tripsByName: false,
|
||||
remarks: false,
|
||||
// `remarks()` method: support for `getPolyline` field?
|
||||
remarksGetPolyline: false, // `remarks()` method: support for `getPolyline` field?
|
||||
remarksGetPolyline: 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',
|
||||
formatTripReq: 'function',
|
||||
formatRefreshJourneyReq: 'function',
|
||||
formatJourneysReq: 'function',
|
||||
transformJourneysQuery: 'function',
|
||||
|
||||
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';
|
||||
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');
|
||||
import {products} from './products.js';
|
||||
import {formatLoyaltyCard} from './loyalty-cards.js';
|
||||
import {ageGroup, ageGroupFromAge, ageGroupLabel} from './ageGroup.js';
|
||||
|
||||
const transformReqBody = (ctx, body) => {
|
||||
return body;
|
||||
};
|
||||
|
||||
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
|
||||
import {products} from '../../lib/products.js';
|
||||
import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
|
||||
import {formatLocationFilter} from './location-filter.js';
|
||||
import {formatLocationsReq} from './locations-req.js';
|
||||
import {formatTravellers} from './travellers.js';
|
||||
import {parseTickets, parsePrice} from './tickets.js';
|
||||
|
||||
const profile = {
|
||||
...baseProfile,
|
||||
locale: 'de-DE',
|
||||
timezone: 'Europe/Berlin',
|
||||
addChecksum: true,
|
||||
|
||||
transformReqBody,
|
||||
transformJourneysQuery,
|
||||
products,
|
||||
formatJourneysReq,
|
||||
formatRefreshJourneyReq,
|
||||
|
||||
products: products,
|
||||
|
||||
parseLocation: parseHook(_parseLocation, parseLocWithDetails),
|
||||
parseJourney: parseHook(_parseJourney, parseJourneyWithPriceAndTickets),
|
||||
parseJourneyLeg: parseHook(_parseJourneyLeg, parseJourneyLegWithLoadFactor),
|
||||
parseLine: parseHook(_parseLine, parseLineWithAdditionalName),
|
||||
parseArrival: parseHook(_parseArrival, parseArrOrDepWithLoadFactor),
|
||||
parseDeparture: parseHook(_parseDeparture, parseArrOrDepWithLoadFactor),
|
||||
parseDateTime,
|
||||
parseLoadFactor,
|
||||
parseHintByCode,
|
||||
formatStation,
|
||||
formatLocationsReq,
|
||||
formatLocationFilter,
|
||||
parsePrice,
|
||||
parseTickets,
|
||||
formatTravellers,
|
||||
|
||||
generateUnreliableTicketUrls: false,
|
||||
refreshJourneyUseOutReconL: true,
|
||||
|
@ -542,7 +29,7 @@ const profile = {
|
|||
journeysFromTrip: true,
|
||||
radar: true,
|
||||
reachableFrom: true,
|
||||
lines: false, // `.svcResL[0].res.lineL[]` is missing 🤔
|
||||
lines: false,
|
||||
};
|
||||
|
||||
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 DEPARTURE = 'd';
|
||||
|
||||
|
@ -23,6 +21,7 @@ const createParseArrOrDep = (prefix) => {
|
|||
remarks: [],
|
||||
origin: profile.parseLocation(ctx, d.transport?.origin || d.origin) || null,
|
||||
destination: profile.parseLocation(ctx, d.transport?.destination || d.destination) || null,
|
||||
// loadFactor: profile.parseArrOrDepWithLoadFactor(ctx, d)
|
||||
};
|
||||
|
||||
// TODO pos
|
||||
|
@ -33,7 +32,7 @@ const createParseArrOrDep = (prefix) => {
|
|||
}
|
||||
|
||||
if (opt.remarks) {
|
||||
res.remarks = parseRemarks(ctx, d);
|
||||
res.remarks = profile.parseRemarks(ctx, d);
|
||||
}
|
||||
// TODO opt.stopovers
|
||||
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});
|
||||
}
|
||||
|
||||
const load = profile.parseLoadFactor(opt, pt.auslastungsmeldungen);
|
||||
if (load) {
|
||||
res.loadFactor = load;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,12 @@ const parseJourney = (ctx, j) => { // j = raw journey
|
|||
// 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;
|
||||
};
|
||||
|
||||
|
|
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
|
||||
|
||||
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]) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const parseBitmask = ({profile}, bitmask) => {
|
||||
const parseProducts = ({profile}, bitmask) => {
|
||||
const res = {};
|
||||
for (let product of profile.products) {
|
||||
res[product.id] = Boolean(bitmask.find(p => p == product.vendo));
|
||||
|
@ -7,5 +7,5 @@ const parseBitmask = ({profile}, bitmask) => {
|
|||
};
|
||||
|
||||
export {
|
||||
parseBitmask,
|
||||
parseProducts,
|
||||
};
|
|
@ -2,7 +2,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../../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 {profile} = client;
|
||||
|
@ -69,7 +69,7 @@ tap.test('formats a journeys() request correctly (DB)', (t) => {
|
|||
|
||||
// transformJourneysQuery() mutates its 2nd argument!
|
||||
const query = {...berlinWienQuery0};
|
||||
const req = profile.transformJourneysQuery(ctx, query);
|
||||
const req = profile.formatJourneysReq(ctx, query);
|
||||
|
||||
t.same(req.body, {
|
||||
...berlinWienQuery0,
|
||||
|
@ -96,7 +96,7 @@ tap.test('formats a journeys() request with BC correctly (DB)', (t) => {
|
|||
|
||||
// transformJourneysQuery() mutates its 2nd argument!
|
||||
const query = {...berlinWienQuery0};
|
||||
const req = profile.transformJourneysQuery(ctx, query);
|
||||
const req = profile.formatJourneysReq(ctx, query);
|
||||
|
||||
t.same(req.body, {
|
||||
...berlinWienQuery0,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import tap from 'tap';
|
||||
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 = {
|
||||
parseLocation: parse,
|
||||
parseStationName: (_, name) => name.toLowerCase(),
|
||||
parseProductsBitmask,
|
||||
parseProducts,
|
||||
products: [{
|
||||
id: 'nationalExpress',
|
||||
vendo: 'ICE',
|
||||
|
|
Loading…
Add table
Reference in a new issue