This commit is contained in:
Traines 2024-12-08 21:42:57 +00:00
parent 65aae69481
commit f8a79834b3
31 changed files with 766 additions and 875 deletions

33
api.js
View file

@ -1,26 +1,31 @@
import {createClient} from './index.js' import {createClient} from './index.js';
import {profile as dbProfile} from './p/db/index.js' import {profile as dbProfile} from './p/db/index.js';
import {createHafasRestApi as createApi} from 'hafas-rest-api' import {createHafasRestApi as createApi} from 'hafas-rest-api';
const config = { const config = {
hostname: process.env.HOSTNAME || 'localhost', hostname: process.env.HOSTNAME || 'localhost',
port: process.env.PORT ? parseInt(process.env.PORT) : 3000, port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
name: "db-vendo-client", name: 'db-vendo-client',
description: "db-vendo-client", description: 'db-vendo-client',
homepage: "https://github.com/public-transport/db-vendo-client", homepage: 'https://github.com/public-transport/db-vendo-client',
version: "6.0.0", version: '6.0.0',
docsLink: 'https://github.com/public-transport/db-vendo-client', docsLink: 'https://github.com/public-transport/db-vendo-client',
openapiSpec: true, openapiSpec: true,
logging: true, logging: true,
aboutPage: true, aboutPage: true,
etags: 'strong', etags: 'strong',
csp: `default-src 'none' style-src 'self' 'unsafe-inline' img-src https:`, csp: 'default-src \'none\' style-src \'self\' \'unsafe-inline\' img-src https:',
} };
const start = async () => {
const vendo = createClient(dbProfile, 'my-hafas-rest-api');
const api = await createApi(vendo, config);
const vendo = createClient(dbProfile, 'my-hafas-rest-api') api.listen(config.port, (err) => {
const api = await createApi(vendo, config) if (err) {
console.error(err);
}
});
};
api.listen(3000, (err) => { start();
if (err) console.error(err)
})

View file

@ -1,8 +1,8 @@
const formatLocationFilter = (stops, addresses, poi) => { const formatLocationFilter = (stops, addresses, poi) => {
if (!addresses && !poi) { // TODO other combos? if (!addresses && !poi) { // TODO other combos?
return 'HALTESTELLEN'; return 'HALTESTELLEN';
} }
return 'ALL'; return 'ALL';
}; };
export { export {

View file

@ -3,12 +3,12 @@ const formatLocationsReq = (ctx, query) => {
return { return {
endpoint: profile.locationsEndpoint, endpoint: profile.locationsEndpoint,
query: { query: {
typ: profile.formatLocationFilter(opt.stops, opt.addresses, opt.poi), typ: profile.formatLocationFilter(opt.stops, opt.addresses, opt.poi),
suchbegriff: query, suchbegriff: query,
limit: opt.results, limit: opt.results,
}, },
method: 'get' method: 'get',
}; };
}; };

View file

@ -12,7 +12,7 @@ const formatNearbyReq = (ctx, location) => {
// TODO getStops: Boolean(opt.stops), // TODO getStops: Boolean(opt.stops),
maxNo: opt.results, maxNo: opt.results,
}, },
method: 'get' method: 'get',
}; };
}; };

View file

@ -2,7 +2,7 @@ import isObj from 'lodash/isObject.js';
const hasProp = (o, k) => Object.prototype.hasOwnProperty.call(o, k); const hasProp = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
const formatProductsFilter = (ctx, filter, key='vendo') => { const formatProductsFilter = (ctx, filter, key = 'vendo') => {
if (!isObj(filter)) { if (!isObj(filter)) {
throw new TypeError('products filter must be an object'); throw new TypeError('products filter must be an object');
} }

View file

@ -3,24 +3,24 @@ const formatStationBoardReq = (ctx, station, type) => {
return { return {
endpoint: profile.boardEndpoint, endpoint: profile.boardEndpoint,
path: type+'/'+station, path: type + '/' + station,
query: { query: {
// TODO direction // TODO direction
filterTransports: profile.formatProductsFilter(ctx, opt.products || {}, 'ris'), filterTransports: profile.formatProductsFilter(ctx, opt.products || {}, 'ris'),
timeStart: profile.formatTime(profile, opt.when, true), timeStart: profile.formatTime(profile, opt.when, true),
timeEnd: profile.formatTime(profile, opt.when.getTime()+opt.duration*60*1000, true), timeEnd: profile.formatTime(profile, opt.when.getTime() + opt.duration * 60 * 1000, true),
maxViaStops: opt.stopovers ? undefined : 0, maxViaStops: opt.stopovers ? undefined : 0,
includeStationGroup: opt.includeRelatedStations, includeStationGroup: opt.includeRelatedStations,
maxTransportsPerType: opt.results === Infinity ? undefined : opt.results, maxTransportsPerType: opt.results === Infinity ? undefined : opt.results,
includeMessagesDisruptions: opt.remarks, includeMessagesDisruptions: opt.remarks,
sortBy: 'TIME_SCHEDULE' sortBy: 'TIME_SCHEDULE',
}, },
method: 'get', method: 'get',
headers: { headers: {
'Db-Client-Id': process.env.DB_CLIENT_ID, 'Db-Client-Id': process.env.DB_CLIENT_ID,
'Db-Api-Key': process.env.DB_API_KEY, 'Db-Api-Key': process.env.DB_API_KEY,
'Accept': 'application/vnd.de.db.ris+json' 'Accept': 'application/vnd.de.db.ris+json',
} },
}; };
}; };

View file

@ -17,7 +17,7 @@ const formatTime = (profile, when, includeOffset = false) => {
zone: timezone, zone: timezone,
}) })
.startOf('second') .startOf('second')
.toISO({ includeOffset: includeOffset, suppressMilliseconds: true }) .toISO({includeOffset: includeOffset, suppressMilliseconds: true});
}; };
export { export {

View file

@ -198,9 +198,9 @@ const createClient = (profile, userAgent, opt = {}) => {
// TODO // TODO
// todo: this is actually "take additional stations nearby the given start and destination station into account" // todo: this is actually "take additional stations nearby the given start and destination station into account"
// see rest.exe docs // see rest.exe docs
//ushrp: Boolean(opt.startWithWalking), // ushrp: Boolean(opt.startWithWalking),
}; };
if (journeysRef) { TODO if (journeysRef) {
query.pagingReference = journeysRef; query.pagingReference = journeysRef;
} else { } else {
query.anfrageZeitpunkt = profile.formatTime(profile, when); query.anfrageZeitpunkt = profile.formatTime(profile, when);
@ -219,7 +219,7 @@ const createClient = (profile, userAgent, opt = {}) => {
earlierRef: res.verbindungReference?.earlier || null, earlierRef: res.verbindungReference?.earlier || null,
laterRef: res.verbindungReference?.later || null, laterRef: res.verbindungReference?.later || null,
journeys, journeys,
realtimeDataUpdatedAt: null // TODO realtimeDataUpdatedAt: null, // TODO
}; };
}; };
@ -417,7 +417,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatLocationsReq({profile, opt}, query); const req = profile.formatLocationsReq({profile, opt}, query);
const {res, common} = await profile.request({profile, opt}, userAgent, req); const {res, common} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res}; const ctx = {profile, opt, common, res};
return res.map(loc => profile.parseLocation(ctx, loc)); return res.map(loc => profile.parseLocation(ctx, loc));

View file

@ -3,12 +3,10 @@ import {isIP} from 'net';
import {Agent as HttpsAgent} from 'https'; import {Agent as HttpsAgent} from 'https';
import roundRobin from '@derhuerst/round-robin-scheduler'; import roundRobin from '@derhuerst/round-robin-scheduler';
import {randomBytes} from 'crypto'; import {randomBytes} from 'crypto';
import createHash from 'create-hash';
import {Buffer} from 'node:buffer';
import {stringify} from 'qs'; import {stringify} from 'qs';
import {Request, fetch} from 'cross-fetch'; import {Request, fetch} from 'cross-fetch';
import {parse as parseContentType} from 'content-type'; import {parse as parseContentType} from 'content-type';
import {HafasError, byErrorCode} from './errors.js'; import {HafasError} from './errors.js';
const proxyAddress = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || null; const proxyAddress = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || null;
const localAddresses = process.env.LOCAL_ADDRESS || null; const localAddresses = process.env.LOCAL_ADDRESS || null;
@ -97,12 +95,12 @@ const checkIfResponseIsOk = (_) => {
}; };
const request = async (ctx, userAgent, reqData) => { const request = async (ctx, userAgent, reqData) => {
const {profile, opt} = ctx; const {profile} = ctx;
const endpoint = reqData.endpoint; const endpoint = reqData.endpoint;
delete reqData.endpoint; delete reqData.endpoint;
const rawReqBody = profile.transformReqBody(ctx, reqData.body); const rawReqBody = profile.transformReqBody(ctx, reqData.body);
//console.log(rawReqBody, JSON.stringify(rawReqBody.req.reisende)); // console.log(rawReqBody, JSON.stringify(rawReqBody.req.reisende));
const req = profile.transformReq(ctx, { const req = profile.transformReq(ctx, {
agent: getAgent(), agent: getAgent(),
method: reqData.method, method: reqData.method,

View file

@ -14,11 +14,11 @@ const ageGroup = {
}; };
const ageGroupLabel = { const ageGroupLabel = {
'B': 'KLEINKIND', B: 'KLEINKIND',
'K': 'FAMILIENKIND', K: 'FAMILIENKIND',
'Y': 'JUGENDLICHER', Y: 'JUGENDLICHER',
'E': 'ERWACHSENER', E: 'ERWACHSENER',
'S': 'SENIOR', S: 'SENIOR',
}; };
const ageGroupFromAge = (age) => { const ageGroupFromAge = (age) => {

View file

@ -3,7 +3,6 @@
import {createRequire} from 'module'; import {createRequire} from 'module';
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
import trim from 'lodash/trim.js';
import uniqBy from 'lodash/uniqBy.js'; import uniqBy from 'lodash/uniqBy.js';
import slugg from 'slugg'; import slugg from 'slugg';
import without from 'lodash/without.js'; import without from 'lodash/without.js';
@ -17,13 +16,11 @@ import {parseDeparture as _parseDeparture} from '../../parse/departure.js';
import {parseLocation as _parseLocation} from '../../parse/location.js'; import {parseLocation as _parseLocation} from '../../parse/location.js';
import {formatStation as _formatStation} from '../../format/station.js'; import {formatStation as _formatStation} from '../../format/station.js';
import {parseDateTime} from '../../parse/date-time.js'; import {parseDateTime} from '../../parse/date-time.js';
import {bike} from '../../format/filters.js';
const baseProfile = require('./base.json'); const baseProfile = require('./base.json');
import {products} from './products.js'; import {products} from './products.js';
import {formatLoyaltyCard} from './loyalty-cards.js'; import {formatLoyaltyCard} from './loyalty-cards.js';
import {ageGroup, ageGroupFromAge, ageGroupLabel} from './ageGroup.js'; import {ageGroup, ageGroupFromAge, ageGroupLabel} from './ageGroup.js';
import {routingModes} from './routing-modes.js';
const transformReqBody = (ctx, body) => { const transformReqBody = (ctx, body) => {
return body; return body;
@ -162,7 +159,8 @@ const parseLoadFactor = (opt, auslastung) => {
}; };
const parseArrOrDepWithLoadFactor = ({parsed, res, opt}, d) => { const parseArrOrDepWithLoadFactor = ({parsed, res, opt}, d) => {
/*const load = parseLoadFactor(opt, d);
/* const load = parseLoadFactor(opt, d);
if (load) { if (load) {
parsed.loadFactor = load; parsed.loadFactor = load;
}*/ // TODO }*/ // TODO
@ -187,10 +185,10 @@ Pass in just opt.age, and the age group will calculated automatically.`);
typ: ageGroupLabel[tvlrAgeGroup || ageGroup.ADULT], typ: ageGroupLabel[tvlrAgeGroup || ageGroup.ADULT],
anzahl: 1, anzahl: 1,
alter: 'age' in opt alter: 'age' in opt
? [opt.age+''] ? [String(opt.age)]
: [], : [],
ermaessigungen: [formatLoyaltyCard(opt.loyaltyCard)] ermaessigungen: [formatLoyaltyCard(opt.loyaltyCard)],
}] }],
}; };
return basicCtrfReq; return basicCtrfReq;
}; };
@ -200,7 +198,7 @@ const transformJourneysQuery = ({profile, opt}, query) => {
return { return {
endpoint: profile.journeysEndpoint, endpoint: profile.journeysEndpoint,
body: query, body: query,
method: 'post' method: 'post',
}; };
}; };
@ -225,55 +223,6 @@ const formatRefreshJourneyReq = (ctx, refreshToken) => {
}; };
}; };
const parseShpCtx = (addDataTicketInfo) => {
try {
return JSON.parse(atob(addDataTicketInfo)).shpCtx;
} catch (e) {
// in case addDataTicketInfo is not a valid base64 string
return null;
}
};
const addDbOfferSelectionUrl = (journey, opt) => {
// if no ticket contains addData, we can't get the offer selection URL
if (journey.tickets.some((t) => t.addDataTicketInfo)) {
const queryParams = new URLSearchParams();
// Add individual parameters
queryParams.append('A.1', opt.age);
queryParams.append('E', 'F');
queryParams.append('E.1', opt.loyaltyCard ? formatLoyaltyCard(opt.loyaltyCard) : '0');
queryParams.append('K', opt.firstClass ? '1' : '2');
queryParams.append('M', 'D');
queryParams.append('RT.1', 'E');
queryParams.append('SS', journey.legs[0].origin.id);
queryParams.append('T', journey.legs[0].departure);
queryParams.append('VH', journey.refreshToken);
queryParams.append('ZS', journey.legs[journey.legs.length - 1].destination.id);
queryParams.append('journeyOptions', '0');
queryParams.append('journeyProducts', '1023');
queryParams.append('optimize', '1');
queryParams.append('returnurl', 'dbnavigator://');
const endpoint = opt.language === 'de' ? 'dox' : 'eox';
journey.tickets.forEach((t) => {
const shpCtx = parseShpCtx(t.addDataTicketInfo);
if (shpCtx) {
const url = new URL(`https://mobile.bahn.de/bin/mobil/query.exe/${endpoint}`);
url.searchParams = new URLSearchParams(queryParams);
url.searchParams.append('shpCtx', shpCtx);
t.url = url.href;
} else {
t.url = null;
}
});
}
};
// todo: fix this // todo: fix this
// line: { // line: {
// type: 'line', // type: 'line',
@ -311,66 +260,9 @@ const mutateToAddPrice = (parsed, raw) => {
return parsed; return parsed;
}; };
const isFirstClassTicket = (addData, opt) => {
// if addData is undefined, it is assumed that the ticket is not first class
// (this is the case for S-Bahn tickets)
if (!addData) {
return false;
}
try {
const addDataJson = JSON.parse(atob(addData));
return Boolean(addDataJson.Upsell === 'S1' || opt.firstClass);
} catch (err) {
return false;
}
};
const mutateToAddTickets = (parsed, opt, j) => {
if (
j.trfRes
&& Array.isArray(j.trfRes.fareSetL)
) {
const addData = j.trfRes.fareSetL[0].addData;
parsed.tickets = j.trfRes.fareSetL
.filter(s => Array.isArray(s.fareL) && s.fareL.length > 0)
.map((s) => {
const fare = s.fareL[0];
if (!fare.ticketL) { // if journeys()
return {
name: fare.buttonText,
priceObj: {amount: fare.price.amount},
};
} else { // if refreshJourney()
return {
name: fare.name || fare.ticketL[0].name,
priceObj: fare.ticketL[0].price,
addData: addData,
addDataTicketInfo: s.addData,
addDataTicketDetails: fare.addData,
addDataTravelInfo: fare.ticketL[0].addData,
firstClass: isFirstClassTicket(s.addData, opt),
};
}
});
// add price info, to avoid breaking changes
// todo [breaking]: remove this format
if (parsed.tickets.length > 0 && !parsed.price) {
parsed.price = {
...parsed.tickets[0].priceObj,
amount: parsed.tickets[0].priceObj.amount / 100,
currency: 'EUR',
};
}
if (opt.generateUnreliableTicketUrls) {
addDbOfferSelectionUrl(parsed, opt);
}
}
};
const parseJourneyWithPriceAndTickets = ({parsed, opt}, raw) => { const parseJourneyWithPriceAndTickets = ({parsed, opt}, raw) => {
mutateToAddPrice(parsed, raw); mutateToAddPrice(parsed, raw);
//mutateToAddTickets(parsed, opt, raw); TODO // mutateToAddTickets(parsed, opt, raw); TODO
return parsed; return parsed;
}; };
@ -574,17 +466,9 @@ const hintsByCode = Object.assign(Object.create(null), {
}, },
}); });
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 parseHintByCode = (raw) => { const parseHintByCode = (raw) => {
const hint = hintsByCode[raw.key.trim().toLowerCase()]; const hint = hintsByCode[raw.key.trim()
.toLowerCase()];
if (hint) { if (hint) {
return Object.assign({text: raw.value}, hint); return Object.assign({text: raw.value}, hint);
} }

View file

@ -1,6 +1,3 @@
import {deepStrictEqual as eql} from 'node:assert';
// todo: generate from https://reiseauskunft.bahn.de/addons/fachkonfig-utf8.cfg ?
const c = { const c = {
NONE: Symbol('no loyalty card'), NONE: Symbol('no loyalty card'),
BAHNCARD: Symbol('Bahncard'), BAHNCARD: Symbol('Bahncard'),
@ -15,42 +12,42 @@ const c = {
const formatLoyaltyCard = (data) => { const formatLoyaltyCard = (data) => {
if (!data) { if (!data) {
return { return {
"art": "KEINE_ERMAESSIGUNG", art: 'KEINE_ERMAESSIGUNG',
"klasse": "KLASSENLOS" klasse: 'KLASSENLOS',
} };
} }
const cls = data.class === 1 ? 'KLASSE_1' : 'KLASSE_2'; const cls = data.class === 1 ? 'KLASSE_1' : 'KLASSE_2';
if (data.type === c.BAHNCARD) { if (data.type === c.BAHNCARD) {
return { return {
art: 'BAHNCARD'+data.discount, art: 'BAHNCARD' + data.discount,
klasse: cls klasse: cls,
} };
} }
if (data.type === c.VORTEILSCARD) { if (data.type === c.VORTEILSCARD) {
return { return {
art: 'A-VORTEILSCARD', art: 'A-VORTEILSCARD',
klasse: 'KLASSENLOS' klasse: 'KLASSENLOS',
} };
} }
if (data.type === c.HALBTAXABO) { if (data.type === c.HALBTAXABO) {
return { return {
art: 'CH-HALBTAXABO_OHNE_RAILPLUS', art: 'CH-HALBTAXABO_OHNE_RAILPLUS',
klasse: 'KLASSENLOS' klasse: 'KLASSENLOS',
} };
} }
// TODO Rest // TODO Rest
if (data.type === c.GENERALABONNEMENT) { if (data.type === c.GENERALABONNEMENT) {
return { return {
art: 'CH-GENERAL-ABONNEMENT', art: 'CH-GENERAL-ABONNEMENT',
klasse: cls klasse: cls,
} };
} }
return { return {
"art": "KEINE_ERMAESSIGUNG", art: 'KEINE_ERMAESSIGUNG',
"klasse": "KLASSENLOS" klasse: 'KLASSENLOS',
} };
}; };
export { export {
c as data, c as data,
formatLoyaltyCard formatLoyaltyCard,
}; };

View file

@ -16,8 +16,8 @@ const createParseArrOrDep = (prefix) => {
stop: profile.parseLocation(ctx, d.station), stop: profile.parseLocation(ctx, d.station),
...profile.parseWhen(ctx, null, d.timeSchedule, d.time, d.canceled), ...profile.parseWhen(ctx, null, d.timeSchedule, d.time, d.canceled),
...profile.parsePlatform(ctx, d.platformSchedule, d.platform, d.canceled), ...profile.parsePlatform(ctx, d.platformSchedule, d.platform, d.canceled),
//prognosisType: TODO // prognosisType: TODO
direction: d.transport?.direction?.stopPlaces?.length > 0 && profile.parseStationName(ctx, d.transport?.direction?.stopPlaces[0].name) || null, direction: d.transport?.direction?.stopPlaces?.length > 0 && profile.parseStationName(ctx, d.transport?.direction?.stopPlaces[0].name) || null,
provenance: profile.parseStationName(ctx, d.transport?.origin?.name) || null, provenance: profile.parseStationName(ctx, d.transport?.origin?.name) || null,
line: profile.parseLine(ctx, d) || null, line: profile.parseLine(ctx, d) || null,
remarks: [], remarks: [],

View file

@ -5,8 +5,8 @@ const locationFallback = (id, name) => {
type: 'stop', type: 'stop',
id: id, id: id,
name: name, name: name,
location: null location: null,
} };
}; };
const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
@ -14,7 +14,7 @@ const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
const res = { const res = {
origin: pt.halte?.length > 0 ? profile.parseLocation(ctx, pt.halte[0]) : locationFallback(pt.abfahrtsOrtExtId, pt.abfahrtsOrt), origin: pt.halte?.length > 0 ? profile.parseLocation(ctx, pt.halte[0]) : locationFallback(pt.abfahrtsOrtExtId, pt.abfahrtsOrt),
destination: pt.halte?.length > 0 ? profile.parseLocation(ctx, pt.halte[pt.halte.length-1]) : locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt), destination: pt.halte?.length > 0 ? profile.parseLocation(ctx, pt.halte[pt.halte.length - 1]) : locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt),
}; };
const cancelledDep = pt.halte?.length > 0 && isStopCancelled(pt.halte[0]); const cancelledDep = pt.halte?.length > 0 && isStopCancelled(pt.halte[0]);
@ -26,7 +26,7 @@ const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
res.prognosedDeparture = dep.prognosedWhen; res.prognosedDeparture = dep.prognosedWhen;
} }
const cancelledArr = pt.halte?.length > 0 && isStopCancelled(pt.halte[pt.halte.length-1]); const cancelledArr = pt.halte?.length > 0 && isStopCancelled(pt.halte[pt.halte.length - 1]);
const arr = profile.parseWhen(ctx, date, pt.ankunftsZeitpunkt, pt.ezAnkunftsZeitpunkt, cancelledArr); const arr = profile.parseWhen(ctx, date, pt.ankunftsZeitpunkt, pt.ezAnkunftsZeitpunkt, cancelledArr);
res.arrival = arr.when; res.arrival = arr.when;
res.plannedArrival = arr.plannedWhen; res.plannedArrival = arr.plannedWhen;
@ -50,7 +50,7 @@ const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
res.public = true; res.public = true;
res.walking = true; res.walking = true;
res.distance = pt.distanz || null; res.distance = pt.distanz || null;
//TODO res.transfer, res.checkin // TODO res.transfer, res.checkin
} else { } else {
res.tripId = pt.journeyId; res.tripId = pt.journeyId;
res.line = profile.parseLine(ctx, pt) || null; res.line = profile.parseLine(ctx, pt) || null;
@ -58,13 +58,13 @@ const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
// TODO res.currentLocation // TODO res.currentLocation
if (pt.halte?.length > 0) { if (pt.halte?.length > 0) {
const arrPl = profile.parsePlatform(ctx, pt.halte[pt.halte.length-1].gleis, pt.halte[pt.halte.length-1].ezGleis, cancelledArr); const arrPl = profile.parsePlatform(ctx, pt.halte[pt.halte.length - 1].gleis, pt.halte[pt.halte.length - 1].ezGleis, cancelledArr);
res.arrivalPlatform = arrPl.platform; res.arrivalPlatform = arrPl.platform;
res.plannedArrivalPlatform = arrPl.plannedPlatform; res.plannedArrivalPlatform = arrPl.plannedPlatform;
if (arrPl.prognosedPlatform) { if (arrPl.prognosedPlatform) {
res.prognosedArrivalPlatform = arrPl.prognosedPlatform; res.prognosedArrivalPlatform = arrPl.prognosedPlatform;
} }
//res.arrivalPrognosisType = null; // TODO // res.arrivalPrognosisType = null; // TODO
const depPl = profile.parsePlatform(ctx, pt.halte[0].gleis, pt.halte[0].ezGleis, cancelledDep); const depPl = profile.parsePlatform(ctx, pt.halte[0].gleis, pt.halte[0].ezGleis, cancelledDep);
res.departurePlatform = depPl.platform; res.departurePlatform = depPl.platform;
@ -72,14 +72,14 @@ const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
if (depPl.prognosedPlatform) { if (depPl.prognosedPlatform) {
res.prognosedDeparturePlatform = depPl.prognosedPlatform; res.prognosedDeparturePlatform = depPl.prognosedPlatform;
} }
//res.departurePrognosisType = null; // TODO // res.departurePrognosisType = null; // TODO
if (opt.stopovers) { if (opt.stopovers) {
res.stopovers = pt.halte.map(s => profile.parseStopover(ctx, s, date)); res.stopovers = pt.halte.map(s => profile.parseStopover(ctx, s, date));
// filter stations the train passes without stopping, as this doesn't comply with fptf (yet) // filter stations the train passes without stopping, as this doesn't comply with fptf (yet)
res.stopovers = res.stopovers.filter((x) => !x.passBy); res.stopovers = res.stopovers.filter((x) => !x.passBy);
} }
if (opt.remarks) { if (opt.remarks) {
res.remarks = parseRemarks(ctx, pt); res.remarks = parseRemarks(ctx, pt);
} }

View file

@ -24,7 +24,7 @@ const parseJourney = (ctx, j) => { // j = raw journey
// TODO // TODO
if (opt.scheduledDays && j.serviceDays) { if (opt.scheduledDays && j.serviceDays) {
// todo [breaking]: rename to scheduledDates // todo [breaking]: rename to scheduledDates
//res.scheduledDays = profile.parseScheduledDays(ctx, j.serviceDays); // res.scheduledDays = profile.parseScheduledDays(ctx, j.serviceDays);
} }
return res; return res;

View file

@ -6,13 +6,13 @@ const parseLine = (ctx, p) => {
type: 'line', type: 'line',
id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription), // TODO terrible id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription), // TODO terrible
fahrtNr: p.verkehrsmittel?.nummer || p.transport?.number, fahrtNr: p.verkehrsmittel?.nummer || p.transport?.number,
name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription, name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription,
public: true, public: true,
}; };
// TODO res.adminCode // TODO res.adminCode
res.productName = p.verkehrsmittel?.kurzText || p.transport?.category; res.productName = p.verkehrsmittel?.kurzText || p.transport?.category;
const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type) const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type);
res.mode = foundProduct?.mode; res.mode = foundProduct?.mode;
res.product = foundProduct?.id; res.product = foundProduct?.id;

View file

@ -7,7 +7,7 @@ const ADDRESS = 'ADR';
const leadingZeros = /^0+/; const leadingZeros = /^0+/;
const parseLocation = (ctx, l) => { const parseLocation = (ctx, l) => {
const {profile, opt} = ctx; const {profile} = ctx;
if (!l) { if (!l) {
return null; return null;
@ -42,9 +42,9 @@ const parseLocation = (ctx, l) => {
stop.products = profile.parseProductsBitmask(ctx, l.products); stop.products = profile.parseProductsBitmask(ctx, l.products);
} }
//TODO isMeta // TODO isMeta
//TODO station // TODO station
//TODO entrances, lines, transitAuthority, dhid, ids // TODO entrances, lines, transitAuthority, dhid, ids
return stop; return stop;
} }

View file

@ -1,7 +1,7 @@
const parseBitmask = ({profile}, bitmask) => { const parseBitmask = ({profile}, bitmask) => {
const res = {}; const res = {};
for (let product of profile.products) { for (let product of profile.products) {
res[product.id] = !!bitmask.find(p => p == product.vendo); res[product.id] = Boolean(bitmask.find(p => p == product.vendo));
} }
return res; return res;
}; };

View file

@ -1,36 +1,46 @@
import flatMap from 'lodash/flatMap.js'; import flatMap from 'lodash/flatMap.js';
const parseRemarks = (ctx, ref) => { const parseRemarks = (ctx, ref) => {
//TODO ereignisZusammenfassung, priorisierteMeldungen? // TODO ereignisZusammenfassung, priorisierteMeldungen?
return flatMap([ return flatMap([
ref.risNotizen || [], ref.risNotizen || [],
ref.himMeldungen || [], ref.himMeldungen || [],
ref.meldungenAsObject || [], ref.meldungenAsObject || [],
ref.verkehrsmittel?.zugattribute || [], ref.verkehrsmittel?.zugattribute || [],
ref.messages || [], ref.messages || [],
ref.attributes || [], ref.attributes || [],
ref.disruptions || [], ref.disruptions || [],
]).map(remark => { ])
if (remark.kategorie) { .map(remark => {
const res = ctx.profile.parseHintByCode(remark); if (remark.kategorie) {
if (res) return res; const res = ctx.profile.parseHintByCode(remark);
} if (res) {
let type = 'hint'; return res;
if (remark.prioritaet || remark.type) type = 'status'; }
if (!remark.kategorie && remark.key || remark.disruptionID || remark.prioritaet && remark.prioritaet == 'HOCH') type = 'warning'; }
let res = { let type = 'hint';
code: remark.code || remark.key, if (remark.prioritaet || remark.type) {
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || Object.values(remark.descriptions || {}).shift()?.textShort, type = 'status';
text: remark.nachrichtLang || remark.value || remark.text || Object.values(remark.descriptions || {}).shift()?.text, }
type: type, if (!remark.kategorie && remark.key || remark.disruptionID || remark.prioritaet && remark.prioritaet == 'HOCH') {
}; type = 'warning';
if (remark.modDateTime) { }
res.modified = ctx.profile.parseDateTime(ctx, null, remark.modDateTime); let res = {
} code: remark.code || remark.key,
// TODO fromStops, toStops = routeIdxFrom ?? summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || Object.values(remark.descriptions || {})
// TODO prio .shift()?.textShort,
return res; text: remark.nachrichtLang || remark.value || remark.text || Object.values(remark.descriptions || {})
}).filter(remark => remark.code != 'BEF'); .shift()?.text,
type: type,
};
if (remark.modDateTime) {
res.modified = ctx.profile.parseDateTime(ctx, null, remark.modDateTime);
}
// TODO fromStops, toStops = routeIdxFrom ??
// TODO prio
return res;
})
.filter(remark => remark.code != 'BEF');
}; };
/* /*
@ -137,7 +147,7 @@ const parseRemarks = (ctx, ref) => {
"prioritaet": "HOCH", "prioritaet": "HOCH",
"modDateTime": "2024-12-05T19:01:48" "modDateTime": "2024-12-05T19:01:48"
} }
] ]
zugattribute zugattribute
[ [
@ -180,10 +190,10 @@ const parseRemarks = (ctx, ref) => {
*/ */
const isStopCancelled = (ref) => { const isStopCancelled = (ref) => {
return !!ref.risNotizen.find(r => r.key == 'text.realtime.stop.cancelled' || r.type == 'HALT_AUSFALL'); return Boolean(ref.risNotizen.find(r => r.key == 'text.realtime.stop.cancelled' || r.type == 'HALT_AUSFALL'));
} };
export { export {
parseRemarks, parseRemarks,
isStopCancelled isStopCancelled,
}; };

View file

@ -3,7 +3,7 @@ import maxBy from 'lodash/maxBy.js';
import last from 'lodash/last.js'; import last from 'lodash/last.js';
const parseTrip = (ctx, t) => { // t = raw trip const parseTrip = (ctx, t) => { // t = raw trip
const {profile, opt} = ctx; const {profile} = ctx;
// pretend the trip is a leg in a journey // pretend the trip is a leg in a journey
const fakeLeg = { const fakeLeg = {
@ -18,7 +18,7 @@ const parseTrip = (ctx, t) => { // t = raw trip
}; };
const trip = profile.parseJourneyLeg(ctx, fakeLeg); const trip = profile.parseJourneyLeg(ctx, fakeLeg);
trip.id = trip.tripId; //TODO journeyId trip.id = trip.tripId; // TODO journeyId
delete trip.tripId; delete trip.tripId;
delete trip.reachable; delete trip.reachable;

View file

@ -31,7 +31,7 @@ const opt = {
products: {}, products: {},
}; };
tap.test('parses a journey correctly (DB)', (t) => { //TODO DEVI leg tap.test('parses a journey correctly (DB)', (t) => { // TODO DEVI leg
const ctx = {profile, opt, common: null, res}; const ctx = {profile, opt, common: null, res};
const journey = profile.parseJourney(ctx, res.verbindungen[0]); const journey = profile.parseJourney(ctx, res.verbindungen[0]);

View file

@ -1,322 +1,322 @@
const dbArrivals = [ const dbArrivals = [
{ {
"tripId": "20241208-c33bba6c-a73a-3eec-8a64-76356c922ece", tripId: '20241208-c33bba6c-a73a-3eec-8a64-76356c922ece',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "8089100", id: '8089100',
"name": "Berlin Jungfernheide (S)", name: 'Berlin Jungfernheide (S)',
"location": null location: null,
}, },
"when": "2024-12-08T01:00:00+01:00", when: '2024-12-08T01:00:00+01:00',
"plannedWhen": "2024-12-08T01:00:00+01:00", plannedWhen: '2024-12-08T01:00:00+01:00',
"delay": 0, delay: 0,
"platform": "6", platform: '6',
"plannedPlatform": "6", plannedPlatform: '6',
"direction": null, direction: null,
"provenance": "Berlin Beusselstraße", provenance: 'Berlin Beusselstraße',
"line": { line: {
"type": "line", type: 'line',
"id": "s-42-42323", id: 's-42-42323',
"fahrtNr": 42323, fahrtNr: 42323,
"name": "S 42 (42323)", name: 'S 42 (42323)',
"public": true, public: true,
"productName": "S", productName: 'S',
"mode": "train", mode: 'train',
"product": "suburban", product: 'suburban',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "FB", code: 'FB',
"summary": "Fahrradmitnahme begrenzt möglich", summary: 'Fahrradmitnahme begrenzt möglich',
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "8089118", id: '8089118',
"name": "Berlin Beusselstraße", name: 'Berlin Beusselstraße',
"location": null location: null,
}, },
"destination": null destination: null,
}, },
{ {
"tripId": "20241208-89eeca5a-1768-3713-894a-dd088977f42b", tripId: '20241208-89eeca5a-1768-3713-894a-dd088977f42b',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "730985", id: '730985',
"name": "Jungfernheide Bahnhof (S+U), Berlin", name: 'Jungfernheide Bahnhof (S+U), Berlin',
"location": null location: null,
}, },
"when": "2024-12-08T01:00:00+01:00", when: '2024-12-08T01:00:00+01:00',
"plannedWhen": "2024-12-08T01:00:00+01:00", plannedWhen: '2024-12-08T01:00:00+01:00',
"delay": 0, delay: 0,
"platform": null, platform: null,
"plannedPlatform": null, plannedPlatform: null,
"direction": null, direction: null,
"provenance": "Rudow (U), Berlin", provenance: 'Rudow (U), Berlin',
"line": { line: {
"type": "line", type: 'line',
"id": "u-7-15421", id: 'u-7-15421',
"fahrtNr": 15421, fahrtNr: 15421,
"name": "U 7 (15421)", name: 'U 7 (15421)',
"public": true, public: true,
"productName": "U", productName: 'U',
"mode": "train", mode: 'train',
"product": "subway", product: 'subway',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "FB", code: 'FB',
"summary": "Fahrradmitnahme begrenzt möglich", summary: 'Fahrradmitnahme begrenzt möglich',
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint" type: 'hint',
}, },
{ {
"code": "RG", code: 'RG',
"summary": "Behindertengerechtes Fahrzeug", summary: 'Behindertengerechtes Fahrzeug',
"text": "Behindertengerechtes Fahrzeug", text: 'Behindertengerechtes Fahrzeug',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "732218", id: '732218',
"name": "Rudow (U), Berlin", name: 'Rudow (U), Berlin',
"location": null location: null,
}, },
"destination": null destination: null,
}, },
{ {
"tripId": "20241208-2dc4f2d4-a1e1-3bbf-a607-98ff71c927d0", tripId: '20241208-2dc4f2d4-a1e1-3bbf-a607-98ff71c927d0',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "730985", id: '730985',
"name": "Jungfernheide Bahnhof (S+U), Berlin", name: 'Jungfernheide Bahnhof (S+U), Berlin',
"location": null location: null,
}, },
"when": "2024-12-08T01:03:00+01:00", when: '2024-12-08T01:03:00+01:00',
"plannedWhen": "2024-12-08T01:03:00+01:00", plannedWhen: '2024-12-08T01:03:00+01:00',
"delay": 0, delay: 0,
"platform": null, platform: null,
"plannedPlatform": null, plannedPlatform: null,
"direction": null, direction: null,
"provenance": "Goerdelersteg, Berlin", provenance: 'Goerdelersteg, Berlin',
"line": { line: {
"type": "line", type: 'line',
"id": "bus-m21-93424", id: 'bus-m21-93424',
"fahrtNr": 93424, fahrtNr: 93424,
"name": "Bus M21 (93424)", name: 'Bus M21 (93424)',
"public": true, public: true,
"productName": "Bus", productName: 'Bus',
"mode": "bus", mode: 'bus',
"product": "bus", product: 'bus',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "NF", code: 'NF',
"summary": "keine Fahrradbeförderung möglich", summary: 'keine Fahrradbeförderung möglich',
"text": "keine Fahrradbeförderung möglich", text: 'keine Fahrradbeförderung möglich',
"type": "hint" type: 'hint',
}, },
{ {
"code": "RG", code: 'RG',
"summary": "Behindertengerechtes Fahrzeug", summary: 'Behindertengerechtes Fahrzeug',
"text": "Behindertengerechtes Fahrzeug", text: 'Behindertengerechtes Fahrzeug',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "730993", id: '730993',
"name": "Goerdelersteg, Berlin", name: 'Goerdelersteg, Berlin',
"location": null location: null,
}, },
"destination": null destination: null,
}, },
{ {
"tripId": "20241208-6fa6d37c-a1c0-3f84-bdac-0424705bffaf", tripId: '20241208-6fa6d37c-a1c0-3f84-bdac-0424705bffaf',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "8089100", id: '8089100',
"name": "Berlin Jungfernheide (S)", name: 'Berlin Jungfernheide (S)',
"location": null location: null,
}, },
"when": "2024-12-08T01:05:00+01:00", when: '2024-12-08T01:05:00+01:00',
"plannedWhen": "2024-12-08T01:05:00+01:00", plannedWhen: '2024-12-08T01:05:00+01:00',
"delay": 0, delay: 0,
"platform": "5", platform: '5',
"plannedPlatform": "5", plannedPlatform: '5',
"direction": null, direction: null,
"provenance": "Berlin Beusselstraße", provenance: 'Berlin Beusselstraße',
"line": { line: {
"type": "line", type: 'line',
"id": "s-41-41254", id: 's-41-41254',
"fahrtNr": 41254, fahrtNr: 41254,
"name": "S 41 (41254)", name: 'S 41 (41254)',
"public": true, public: true,
"productName": "S", productName: 'S',
"mode": "train", mode: 'train',
"product": "suburban", product: 'suburban',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "FB", code: 'FB',
"summary": "Fahrradmitnahme begrenzt möglich", summary: 'Fahrradmitnahme begrenzt möglich',
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "8089118", id: '8089118',
"name": "Berlin Beusselstraße", name: 'Berlin Beusselstraße',
"location": null location: null,
}, },
"destination": null destination: null,
}, },
{ {
"tripId": "20241208-c4abf007-d667-3bf1-87a8-2d1b153c014d", tripId: '20241208-c4abf007-d667-3bf1-87a8-2d1b153c014d',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "730985", id: '730985',
"name": "Jungfernheide Bahnhof (S+U), Berlin", name: 'Jungfernheide Bahnhof (S+U), Berlin',
"location": null location: null,
}, },
"when": "2024-12-08T01:10:00+01:00", when: '2024-12-08T01:10:00+01:00',
"plannedWhen": "2024-12-08T01:10:00+01:00", plannedWhen: '2024-12-08T01:10:00+01:00',
"delay": 0, delay: 0,
"platform": null, platform: null,
"plannedPlatform": null, plannedPlatform: null,
"direction": null, direction: null,
"provenance": "Rudow (U), Berlin", provenance: 'Rudow (U), Berlin',
"line": { line: {
"type": "line", type: 'line',
"id": "u-7-15422", id: 'u-7-15422',
"fahrtNr": 15422, fahrtNr: 15422,
"name": "U 7 (15422)", name: 'U 7 (15422)',
"public": true, public: true,
"productName": "U", productName: 'U',
"mode": "train", mode: 'train',
"product": "subway", product: 'subway',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "FB", code: 'FB',
"summary": "Fahrradmitnahme begrenzt möglich", summary: 'Fahrradmitnahme begrenzt möglich',
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint" type: 'hint',
}, },
{ {
"code": "RG", code: 'RG',
"summary": "Behindertengerechtes Fahrzeug", summary: 'Behindertengerechtes Fahrzeug',
"text": "Behindertengerechtes Fahrzeug", text: 'Behindertengerechtes Fahrzeug',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "732218", id: '732218',
"name": "Rudow (U), Berlin", name: 'Rudow (U), Berlin',
"location": null location: null,
}, },
"destination": null destination: null,
}, },
{ {
"tripId": "20241208-c8b6e3e4-6acb-3237-b89e-1fca72497555", tripId: '20241208-c8b6e3e4-6acb-3237-b89e-1fca72497555',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "8089100", id: '8089100',
"name": "Berlin Jungfernheide (S)", name: 'Berlin Jungfernheide (S)',
"location": null location: null,
}, },
"when": "2024-12-08T01:10:00+01:00", when: '2024-12-08T01:10:00+01:00',
"plannedWhen": "2024-12-08T01:10:00+01:00", plannedWhen: '2024-12-08T01:10:00+01:00',
"delay": 0, delay: 0,
"platform": "6", platform: '6',
"plannedPlatform": "6", plannedPlatform: '6',
"direction": null, direction: null,
"provenance": "Berlin Beusselstraße", provenance: 'Berlin Beusselstraße',
"line": { line: {
"type": "line", type: 'line',
"id": "s-42-42325", id: 's-42-42325',
"fahrtNr": 42325, fahrtNr: 42325,
"name": "S 42 (42325)", name: 'S 42 (42325)',
"public": true, public: true,
"productName": "S", productName: 'S',
"mode": "train", mode: 'train',
"product": "suburban", product: 'suburban',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "FB", code: 'FB',
"summary": "Fahrradmitnahme begrenzt möglich", summary: 'Fahrradmitnahme begrenzt möglich',
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "8089118", id: '8089118',
"name": "Berlin Beusselstraße", name: 'Berlin Beusselstraße',
"location": null location: null,
}, },
"destination": null destination: null,
}, },
{ {
"tripId": "20241208-f9d83ab7-d603-3344-87c0-a65ecf0f8524", tripId: '20241208-f9d83ab7-d603-3344-87c0-a65ecf0f8524',
"stop": { stop: {
"type": "stop", type: 'stop',
"id": "730985", id: '730985',
"name": "Jungfernheide Bahnhof (S+U), Berlin", name: 'Jungfernheide Bahnhof (S+U), Berlin',
"location": null location: null,
}, },
"when": "2024-12-08T01:10:00+01:00", when: '2024-12-08T01:10:00+01:00',
"plannedWhen": "2024-12-08T01:10:00+01:00", plannedWhen: '2024-12-08T01:10:00+01:00',
"delay": 0, delay: 0,
"platform": null, platform: null,
"plannedPlatform": null, plannedPlatform: null,
"direction": null, direction: null,
"provenance": "Rathaus Spandau (S+U), Berlin", provenance: 'Rathaus Spandau (S+U), Berlin',
"line": { line: {
"type": "line", type: 'line',
"id": "u-7-15752", id: 'u-7-15752',
"fahrtNr": 15752, fahrtNr: 15752,
"name": "U 7 (15752)", name: 'U 7 (15752)',
"public": true, public: true,
"productName": "U", productName: 'U',
"mode": "train", mode: 'train',
"product": "subway", product: 'subway',
"operator": null operator: null,
}, },
"remarks": [ remarks: [
{ {
"code": "FB", code: 'FB',
"summary": "Fahrradmitnahme begrenzt möglich", summary: 'Fahrradmitnahme begrenzt möglich',
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint" type: 'hint',
}, },
{ {
"code": "RG", code: 'RG',
"summary": "Behindertengerechtes Fahrzeug", summary: 'Behindertengerechtes Fahrzeug',
"text": "Behindertengerechtes Fahrzeug", text: 'Behindertengerechtes Fahrzeug',
"type": "hint" type: 'hint',
} },
], ],
"origin": { origin: {
"type": "stop", type: 'stop',
"id": "731176", id: '731176',
"name": "Rathaus Spandau (S+U), Berlin", name: 'Rathaus Spandau (S+U), Berlin',
"location": null location: null,
}, },
"destination": null destination: null,
} },
]; ];
export { export {

View file

@ -30,133 +30,133 @@ const dbJourney = {
departure: '2025-04-11T05:11:00+02:00', departure: '2025-04-11T05:11:00+02:00',
plannedDeparture: '2025-04-11T05:11:00+02:00', plannedDeparture: '2025-04-11T05:11:00+02:00',
departureDelay: null, departureDelay: null,
direction: "Hennef(Sieg)", direction: 'Hennef(Sieg)',
arrivalPlatform: "9", arrivalPlatform: '9',
plannedArrivalPlatform: "9", plannedArrivalPlatform: '9',
departurePlatform: "10 A-B", departurePlatform: '10 A-B',
plannedDeparturePlatform: "10 A-B", plannedDeparturePlatform: '10 A-B',
tripId: "2|#VN#1#ST#1733173731#PI#1#ZI#161473#TA#1#DA#110425#1S#8000208#1T#504#LS#8002753#LT#545#PU#81#RT#1#CA#s#ZE#12#ZB#S 12#PC#4#FR#8000208#FT#504#TO#8002753#TT#545#", tripId: '2|#VN#1#ST#1733173731#PI#1#ZI#161473#TA#1#DA#110425#1S#8000208#1T#504#LS#8002753#LT#545#PU#81#RT#1#CA#s#ZE#12#ZB#S 12#PC#4#FR#8000208#FT#504#TO#8002753#TT#545#',
line: { line: {
type: "line", type: 'line',
id: "s-12", id: 's-12',
fahrtNr: "12", fahrtNr: '12',
name: "S 12", name: 'S 12',
public: true, public: true,
productName: "S", productName: 'S',
mode: "train", mode: 'train',
product: "suburban", product: 'suburban',
operator: { operator: {
"type": "operator", type: 'operator',
"id": "db-regio-ag-nrw", id: 'db-regio-ag-nrw',
"name": "DB Regio AG NRW", name: 'DB Regio AG NRW',
}, },
}, },
remarks: [ remarks: [
{ {
"text": "Fahrradmitnahme begrenzt möglich", text: 'Fahrradmitnahme begrenzt möglich',
"type": "hint", type: 'hint',
"code": "bicycle-conveyance", code: 'bicycle-conveyance',
"summary": "bicycles conveyed", summary: 'bicycles conveyed',
}, },
{ {
"text": "nur 2. Klasse", text: 'nur 2. Klasse',
"type": "hint", type: 'hint',
"code": "2nd-class-only", code: '2nd-class-only',
"summary": "2. class only", summary: '2. class only',
}, },
{ {
"text": "Fahrzeuggebundene Einstiegshilfe vorhanden", text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
"type": "hint", type: 'hint',
"code": "boarding-ramp", code: 'boarding-ramp',
"summary": "vehicle-mounted boarding ramp available", summary: 'vehicle-mounted boarding ramp available',
} },
], ],
"polyline": { polyline: {
"type": "FeatureCollection", type: 'FeatureCollection',
"features": [ features: [
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.9597, 6.9597,
50.943038, 50.943038,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.9597, 6.9597,
50.943038, 50.943038,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.960033, 6.960033,
50.942724, 50.942724,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.960491, 6.960491,
50.942301, 50.942301,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.961282, 6.961282,
50.941825, 50.941825,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.962253, 6.962253,
50.941582, 50.941582,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.971467, 6.971467,
50.941492, 50.941492,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
6.974658, 6.974658,
50.941285, 50.941285,
], ],
@ -198,7 +198,7 @@ const dbJourney = {
id: '8073368', id: '8073368',
latitude: 50.941717, latitude: 50.941717,
longitude: 6.974065, longitude: 6.974065,
} },
}, },
destination: { destination: {
type: 'stop', type: 'stop',
@ -240,10 +240,10 @@ const dbJourney = {
plannedDeparturePlatform: '11', plannedDeparturePlatform: '11',
remarks: [ remarks: [
{ {
"text": "Bordrestaurant", text: 'Bordrestaurant',
"type": "hint", type: 'hint',
"code": "on-board-restaurant", code: 'on-board-restaurant',
"summary": "Bordrestaurant available", summary: 'Bordrestaurant available',
}, },
{ {
text: 'Komfort Check-in verfügbar - wenn möglich bitte einchecken', text: 'Komfort Check-in verfügbar - wenn möglich bitte einchecken',
@ -252,26 +252,26 @@ const dbJourney = {
summary: 'Komfort-Checkin available', summary: 'Komfort-Checkin available',
}, },
], ],
"polyline": { polyline: {
"type": "FeatureCollection", type: 'FeatureCollection',
"features": [ features: [
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
11.082144, 11.082144,
49.445678, 49.445678,
], ],
}, },
}, },
{ {
"type": "Feature", type: 'Feature',
"properties": {}, properties: {},
"geometry": { geometry: {
"type": "Point", type: 'Point',
"coordinates": [ coordinates: [
11.08227, 11.08227,
49.445435, 49.445435,
], ],
@ -282,8 +282,8 @@ const dbJourney = {
}, },
], ],
refreshToken: '¶HKI¶T$A=1@O=Köln Hbf@X=6958730@Y=50943029@L=8000207@a=128@$A=1@O=Köln Messe/Deutz@X=6975000@Y=50940872@L=8003368@a=128@$202504110511$202504110512$S 12$$1$$$$$$§W$A=1@O=Köln Messe/Deutz@X=6975000@Y=50940872@L=8003368@a=128@$A=1@O=Köln Messe/Deutz Gl.11-12@X=6974065@Y=50941717@L=8073368@a=128@$202504110512$202504110519$$$1$$$$$$§T$A=1@O=Köln Messe/Deutz Gl.11-12@X=6974065@Y=50941717@L=8073368@a=128@$A=1@O=Nürnberg Hbf@X=11082989@Y=49445615@L=8000284@a=128@$202504110520$202504110858$ICE 523$$1$$$$$$¶KC¶#VE#2#CF#100#CA#0#CM#0#SICT#0#AM#81#AM2#0#RT#7#¶KCC¶I1ZFIzEjRVJHIzMjSElOIzAjRUNLIzcwNTkxMXw3MDU5MTF8NzA2MTM4fDcwNjEzOHwwfDB8NDg1fDcwNTg5N3wxfDB8MTh8MHwwfC0yMTQ3NDgzNjQ4I0dBTSMxMTA0MjUwNTExIwpaI1ZOIzEjU1QjMTczMzE3MzczMSNQSSMxI1pJIzE2MTQ3MyNUQSMxI0RBIzExMDQyNSMxUyM4MDAwMjA4IzFUIzUwNCNMUyM4MDAyNzUzI0xUIzU0NSNQVSM4MSNSVCMxI0NBI3MjWkUjMTIjWkIjUyAgICAgMTIjUEMjNCNGUiM4MDAwMjA3I0ZUIzUxMSNUTyM4MDAzMzY4I1RUIzUxMiMKRiNWTiMwI1NUIzE3MzMxNzM3MzEjUEkjMSNQVSM4MSNaSSMyMjgzODI4ODkzI0RBIzExMDQyNSNGUiM4MDAzMzY4I1RPIzgwNzMzNjgjRlQjNTEyI1RUIzUxOSNUUyMwI0ZGIyNGViMwIwpaI1ZOIzEjU1QjMTczMzE3MzczMSNQSSMxI1pJIzE1NTA2MyNUQSMwI0RBIzExMDQyNSMxUyM4MDAwMDgwIzFUIzM1OCNMUyM4MDAwMjYxI0xUIzEwMDYjUFUjODEjUlQjMSNDQSNJQ0UjWkUjNTIzI1pCI0lDRSAgNTIzI1BDIzAjRlIjODA3MzM2OCNGVCM1MjAjVE8jODAwMDI4NCNUVCM4NTgj¶KRCC¶#VE#1#¶SC¶1_H4sIAAAAAAACA32P306DMBjFX8X0GpevhUIhIUFGFv8sGzHOaIwXbHQTU2CWskgIz+GbeOXdXswCemE09qLpOT09v68tOnCJPIQnDkMG4q9Kiyic3EYTV2vJX5DXoqLOZ8ijRn8IkQcGKmsVJYrrMAFCwcIYDeZNlvcmUNBLW9uh4RQb6LloZkLJOfIeWqSafR+Lr5eRDuVl2quLxVSLQyLqXmEgJuoeh5mmT7uxWJNTvp+Xm7FGZKlOnvk4WPpXx3dRnJyvt8Gdb7uUOSYE9z4F1zKBuMHKZxDM9QZAwAlC/WbvY8c0scNsmwSZvzq+ATDA1KIs0INUavzgbJgikfJP7OL4IYs1l7svNMbAiMtczbZcy6I2pj/YzPqHTQh2zd/sHVdxKRqRFdpTsuaDdVnWsuBNWNZFWiFvm4hqvIiTqhJZpb6zfFPGiUxyHWq7rvsE0LytQvMBAAA=', refreshToken: '¶HKI¶T$A=1@O=Köln Hbf@X=6958730@Y=50943029@L=8000207@a=128@$A=1@O=Köln Messe/Deutz@X=6975000@Y=50940872@L=8003368@a=128@$202504110511$202504110512$S 12$$1$$$$$$§W$A=1@O=Köln Messe/Deutz@X=6975000@Y=50940872@L=8003368@a=128@$A=1@O=Köln Messe/Deutz Gl.11-12@X=6974065@Y=50941717@L=8073368@a=128@$202504110512$202504110519$$$1$$$$$$§T$A=1@O=Köln Messe/Deutz Gl.11-12@X=6974065@Y=50941717@L=8073368@a=128@$A=1@O=Nürnberg Hbf@X=11082989@Y=49445615@L=8000284@a=128@$202504110520$202504110858$ICE 523$$1$$$$$$¶KC¶#VE#2#CF#100#CA#0#CM#0#SICT#0#AM#81#AM2#0#RT#7#¶KCC¶I1ZFIzEjRVJHIzMjSElOIzAjRUNLIzcwNTkxMXw3MDU5MTF8NzA2MTM4fDcwNjEzOHwwfDB8NDg1fDcwNTg5N3wxfDB8MTh8MHwwfC0yMTQ3NDgzNjQ4I0dBTSMxMTA0MjUwNTExIwpaI1ZOIzEjU1QjMTczMzE3MzczMSNQSSMxI1pJIzE2MTQ3MyNUQSMxI0RBIzExMDQyNSMxUyM4MDAwMjA4IzFUIzUwNCNMUyM4MDAyNzUzI0xUIzU0NSNQVSM4MSNSVCMxI0NBI3MjWkUjMTIjWkIjUyAgICAgMTIjUEMjNCNGUiM4MDAwMjA3I0ZUIzUxMSNUTyM4MDAzMzY4I1RUIzUxMiMKRiNWTiMwI1NUIzE3MzMxNzM3MzEjUEkjMSNQVSM4MSNaSSMyMjgzODI4ODkzI0RBIzExMDQyNSNGUiM4MDAzMzY4I1RPIzgwNzMzNjgjRlQjNTEyI1RUIzUxOSNUUyMwI0ZGIyNGViMwIwpaI1ZOIzEjU1QjMTczMzE3MzczMSNQSSMxI1pJIzE1NTA2MyNUQSMwI0RBIzExMDQyNSMxUyM4MDAwMDgwIzFUIzM1OCNMUyM4MDAwMjYxI0xUIzEwMDYjUFUjODEjUlQjMSNDQSNJQ0UjWkUjNTIzI1pCI0lDRSAgNTIzI1BDIzAjRlIjODA3MzM2OCNGVCM1MjAjVE8jODAwMDI4NCNUVCM4NTgj¶KRCC¶#VE#1#¶SC¶1_H4sIAAAAAAACA32P306DMBjFX8X0GpevhUIhIUFGFv8sGzHOaIwXbHQTU2CWskgIz+GbeOXdXswCemE09qLpOT09v68tOnCJPIQnDkMG4q9Kiyic3EYTV2vJX5DXoqLOZ8ijRn8IkQcGKmsVJYrrMAFCwcIYDeZNlvcmUNBLW9uh4RQb6LloZkLJOfIeWqSafR+Lr5eRDuVl2quLxVSLQyLqXmEgJuoeh5mmT7uxWJNTvp+Xm7FGZKlOnvk4WPpXx3dRnJyvt8Gdb7uUOSYE9z4F1zKBuMHKZxDM9QZAwAlC/WbvY8c0scNsmwSZvzq+ATDA1KIs0INUavzgbJgikfJP7OL4IYs1l7svNMbAiMtczbZcy6I2pj/YzPqHTQh2zd/sHVdxKRqRFdpTsuaDdVnWsuBNWNZFWiFvm4hqvIiTqhJZpb6zfFPGiUxyHWq7rvsE0LytQvMBAAA=',
price: { amount: 31.49, currency: 'EUR', hint: null }, price: {amount: 31.49, currency: 'EUR', hint: null},
remarks: [] remarks: [],
}; };
export { export {

View file

@ -36,30 +36,30 @@ const opt = {
const berlinWienQuery0 = Object.freeze( const berlinWienQuery0 = Object.freeze(
{ {
"abfahrtsHalt": "A=1@L=8098160@", abfahrtsHalt: 'A=1@L=8098160@',
"anfrageZeitpunkt": "2024-12-07T23:50:12", anfrageZeitpunkt: '2024-12-07T23:50:12',
"ankunftsHalt": "A=1@L=8000284@", ankunftsHalt: 'A=1@L=8000284@',
"ankunftSuche": "ABFAHRT", ankunftSuche: 'ABFAHRT',
"klasse": "KLASSE_2", klasse: 'KLASSE_2',
"produktgattungen": [ produktgattungen: [
"ICE", 'ICE',
"EC_IC", 'EC_IC',
"IR", 'IR',
"REGIONAL", 'REGIONAL',
"SBAHN", 'SBAHN',
"BUS", 'BUS',
"SCHIFF", 'SCHIFF',
"UBAHN", 'UBAHN',
"TRAM", 'TRAM',
"ANRUFPFLICHTIG" 'ANRUFPFLICHTIG',
], ],
"schnelleVerbindungen": true, schnelleVerbindungen: true,
"sitzplatzOnly": false, sitzplatzOnly: false,
"bikeCarriage": false, bikeCarriage: false,
"reservierungsKontingenteVorhanden": false, reservierungsKontingenteVorhanden: false,
"nurDeutschlandTicketVerbindungen": false, nurDeutschlandTicketVerbindungen: false,
"deutschlandTicketVorhanden": false deutschlandTicketVorhanden: false,
}); });
tap.test('formats a journeys() request correctly (DB)', (t) => { tap.test('formats a journeys() request correctly (DB)', (t) => {
const _opt = {...opt}; const _opt = {...opt};
@ -75,17 +75,17 @@ tap.test('formats a journeys() request correctly (DB)', (t) => {
...berlinWienQuery0, ...berlinWienQuery0,
reisende: [ reisende: [
{ {
"typ": "ERWACHSENER", typ: 'ERWACHSENER',
"ermaessigungen": [ ermaessigungen: [
{ {
"art": "KEINE_ERMAESSIGUNG", art: 'KEINE_ERMAESSIGUNG',
"klasse": "KLASSENLOS" klasse: 'KLASSENLOS',
} },
], ],
"alter": [], alter: [],
"anzahl": 1 anzahl: 1,
} },
] ],
}); });
t.end(); t.end();
}); });
@ -102,17 +102,17 @@ tap.test('formats a journeys() request with BC correctly (DB)', (t) => {
...berlinWienQuery0, ...berlinWienQuery0,
reisende: [ reisende: [
{ {
"typ": "JUGENDLICHER", typ: 'JUGENDLICHER',
"ermaessigungen": [ ermaessigungen: [
{ {
"art": "BAHNCARD25", art: 'BAHNCARD25',
"klasse": "KLASSE_2" klasse: 'KLASSE_2',
} },
], ],
"alter": ["24"], alter: ['24'],
"anzahl": 1 anzahl: 1,
} },
] ],
}); });
t.end(); t.end();
}); });

View file

@ -33,4 +33,4 @@ tap.test('formatProductsFilter works without customisations', (t) => {
const filter = {}; const filter = {};
t.same(format(ctx, filter), expected); t.same(format(ctx, filter), expected);
t.end(); t.end();
}); });

View file

@ -18,7 +18,7 @@ import {
} from '../../lib/errors.js'; } from '../../lib/errors.js';
import {formatTripReq} from '../../format/trip-req.js'; import {formatTripReq} from '../../format/trip-req.js';
const resNoMatch = {"verbindungen":[],"verbindungReference":{},"fehlerNachricht":{"code":"MDA-AK-MSG-1001","ueberschrift":"Datum liegt außerhalb der Fahrplanperiode.","text":"Das Datum liegt außerhalb der Fahrplanperiode."}}; const resNoMatch = {verbindungen: [], verbindungReference: {}, fehlerNachricht: {code: 'MDA-AK-MSG-1001', ueberschrift: 'Datum liegt außerhalb der Fahrplanperiode.', text: 'Das Datum liegt außerhalb der Fahrplanperiode.'}};
const USER_AGENT = 'public-transport/hafas-client:test'; const USER_AGENT = 'public-transport/hafas-client:test';

View file

@ -22,7 +22,7 @@ const profile = {
default: true, default: true,
}, },
], ],
parseOperator: _ => null parseOperator: _ => null,
}; };
const ctx = { const ctx = {
data: {}, data: {},
@ -32,28 +32,28 @@ const ctx = {
tap.test('parses ICE leg correctly', (t) => { tap.test('parses ICE leg correctly', (t) => {
const input = { const input = {
"journeyId": "foo", journeyId: 'foo',
"verkehrsmittel": { verkehrsmittel: {
"produktGattung": "ICE", produktGattung: 'ICE',
"kategorie": "ICE", kategorie: 'ICE',
"name": "ICE 229", name: 'ICE 229',
"nummer": "229", nummer: '229',
"richtung": "Wien Hbf", richtung: 'Wien Hbf',
"typ": "PUBLICTRANSPORT", typ: 'PUBLICTRANSPORT',
"zugattribute": [{ zugattribute: [{
"kategorie": "BEFÖRDERER", kategorie: 'BEFÖRDERER',
"key": "BEF", key: 'BEF',
"value": "DB Fernverkehr AG, Österreichische Bundesbahnen" value: 'DB Fernverkehr AG, Österreichische Bundesbahnen',
},{ }, {
"kategorie": "FAHRRADMITNAHME", kategorie: 'FAHRRADMITNAHME',
"key": "FR", key: 'FR',
"value": "Bicycles conveyed - subject to reservation", value: 'Bicycles conveyed - subject to reservation',
"teilstreckenHinweis": "(Mainz Hbf - Wien Meidling)" teilstreckenHinweis: '(Mainz Hbf - Wien Meidling)',
}], }],
"kurzText": "ICE", kurzText: 'ICE',
"mittelText": "ICE 229", mittelText: 'ICE 229',
"langText": "ICE 229" langText: 'ICE 229',
} },
}; };
const expected = { const expected = {
type: 'line', type: 'line',
@ -73,18 +73,18 @@ tap.test('parses ICE leg correctly', (t) => {
tap.test('parses Bus trip correctly', (t) => { tap.test('parses Bus trip correctly', (t) => {
const input = { const input = {
"reisetag": "2024-12-07", reisetag: '2024-12-07',
"regulaereVerkehrstage": "not every day", regulaereVerkehrstage: 'not every day',
"irregulaereVerkehrstage": "7., 14. Dec 2024", irregulaereVerkehrstage: '7., 14. Dec 2024',
"zugName": "Bus 807", zugName: 'Bus 807',
"zugattribute": [ zugattribute: [
{ {
"kategorie": "INFORMATION", kategorie: 'INFORMATION',
"key": "cB", key: 'cB',
"value": "Tel. 0981-9714925, Anmeldung bis 90 Min. vor Abfahrt (Mo-So: 9-15 Uhr)" value: 'Tel. 0981-9714925, Anmeldung bis 90 Min. vor Abfahrt (Mo-So: 9-15 Uhr)',
} },
], ],
"cancelled": false, cancelled: false,
}; };
const expected = { const expected = {
type: 'line', type: 'line',
@ -95,7 +95,7 @@ tap.test('parses Bus trip correctly', (t) => {
product: undefined, product: undefined,
productName: undefined, productName: undefined,
mode: undefined, mode: undefined,
operator: null operator: null,
}; };
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
@ -103,29 +103,28 @@ tap.test('parses Bus trip correctly', (t) => {
}); });
tap.test('parses Bus leg correctly', (t) => { tap.test('parses Bus leg correctly', (t) => {
const input = { const input = {
"journeyId": "foo", journeyId: 'foo',
"verkehrsmittel": { verkehrsmittel: {
"produktGattung": "BUS", produktGattung: 'BUS',
"kategorie": "Bus", kategorie: 'Bus',
"linienNummer": "807", linienNummer: '807',
"name": "Bus 807", name: 'Bus 807',
"nummer": "807", nummer: '807',
"richtung": "Bahnhof, Dombühl", richtung: 'Bahnhof, Dombühl',
"typ": "PUBLICTRANSPORT", typ: 'PUBLICTRANSPORT',
"zugattribute": [ zugattribute: [
{ {
"kategorie": "INFORMATION", kategorie: 'INFORMATION',
"key": "cB", key: 'cB',
"value": "Tel. 0981-9714925, Anmeldung bis 90 Min. vor Abfahrt (Mo-So: 9-15 Uhr)" value: 'Tel. 0981-9714925, Anmeldung bis 90 Min. vor Abfahrt (Mo-So: 9-15 Uhr)',
} },
], ],
"kurzText": "Bus", kurzText: 'Bus',
"mittelText": "Bus 807", mittelText: 'Bus 807',
"langText": "Bus 807" langText: 'Bus 807',
} },
}; };
const expected = { const expected = {
type: 'line', type: 'line',
@ -136,7 +135,7 @@ tap.test('parses Bus leg correctly', (t) => {
product: 'bus', product: 'bus',
productName: 'Bus', productName: 'Bus',
mode: 'bus', mode: 'bus',
operator: null operator: null,
}; };
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
@ -144,20 +143,19 @@ tap.test('parses Bus leg correctly', (t) => {
}); });
tap.test('parses ris entry correctly', (t) => { tap.test('parses ris entry correctly', (t) => {
const input = { const input = {
"journeyID": "20241207-79693bf3-2ed5-325f-8a99-154bad5f5cf3", journeyID: '20241207-79693bf3-2ed5-325f-8a99-154bad5f5cf3',
"transport": { transport: {
"type": "HIGH_SPEED_TRAIN", type: 'HIGH_SPEED_TRAIN',
"journeyDescription": "RB 51 (15538)", journeyDescription: 'RB 51 (15538)',
"label": "", label: '',
"category": "RB", category: 'RB',
"categoryInternal": "RB", categoryInternal: 'RB',
"number": 15538, number: 15538,
"line": "51", line: '51',
"replacementTransport": null, replacementTransport: null,
} },
}; };
const expected = { const expected = {
type: 'line', type: 'line',
@ -168,7 +166,7 @@ tap.test('parses ris entry correctly', (t) => {
product: 'nationalExpress', product: 'nationalExpress',
productName: 'RB', productName: 'RB',
mode: 'train', mode: 'train',
operator: null operator: null,
}; };
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);

View file

@ -1,7 +1,6 @@
import tap from 'tap'; import tap from 'tap';
import omit from 'lodash/omit.js';
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 {parseBitmask as parseProductsBitmask} from '../../parse/products-bitmask.js';
const profile = { const profile = {
parseLocation: parse, parseLocation: parse,
@ -26,7 +25,7 @@ const profile = {
{ {
id: 'taxi', id: 'taxi',
vendo: 'ANRUFPFLICHTIG', vendo: 'ANRUFPFLICHTIG',
}] }],
}; };
const ctx = { const ctx = {
@ -41,12 +40,12 @@ const ctx = {
tap.test('parses an address correctly', (t) => { tap.test('parses an address correctly', (t) => {
const input = { const input = {
"id": "A=2@O=Würzburg - Heuchelhof, Pergamonweg@X=9952209@Y=49736794@U=92@b=981423354@B=1@p=1706613073@", id: 'A=2@O=Würzburg - Heuchelhof, Pergamonweg@X=9952209@Y=49736794@U=92@b=981423354@B=1@p=1706613073@',
"lat": 49.736794, lat: 49.736794,
"lon": 9.952209, lon: 9.952209,
"name": "Würzburg - Heuchelhof, Pergamonweg", name: 'Würzburg - Heuchelhof, Pergamonweg',
"products": [], products: [],
"type": "ADR" type: 'ADR',
}; };
const address = parse(ctx, input); const address = parse(ctx, input);
@ -63,12 +62,12 @@ tap.test('parses an address correctly', (t) => {
tap.test('parses a POI correctly', (t) => { tap.test('parses a POI correctly', (t) => {
const input = { const input = {
"id": "A=4@O=Berlin, Pergamonkeller (Gastronomie)@X=13395473@Y=52520223@U=91@L=991526508@B=1@p=1732715706@", id: 'A=4@O=Berlin, Pergamonkeller (Gastronomie)@X=13395473@Y=52520223@U=91@L=991526508@B=1@p=1732715706@',
"lat": 52.52022, lat: 52.52022,
"lon": 13.395473, lon: 13.395473,
"name": "Berlin, Pergamonkeller (Gastronomie)", name: 'Berlin, Pergamonkeller (Gastronomie)',
"products": [], products: [],
"type": "POI" type: 'POI',
}; };
const poi = parse(ctx, input); const poi = parse(ctx, input);
@ -85,18 +84,18 @@ tap.test('parses a POI correctly', (t) => {
tap.test('parses a stop correctly', (t) => { tap.test('parses a stop correctly', (t) => {
const input = { const input = {
"extId": "8012622", extId: '8012622',
"id": "A=1@O=Perleberg@X=11852322@Y=53071252@U=81@L=8012622@B=1@p=1733173731@i=U×008027183@", id: 'A=1@O=Perleberg@X=11852322@Y=53071252@U=81@L=8012622@B=1@p=1733173731@i=U×008027183@',
"lat": 53.07068, lat: 53.07068,
"lon": 11.85039, lon: 11.85039,
"name": "Perleberg", name: 'Perleberg',
"products": [ products: [
"REGIONAL", 'REGIONAL',
"BUS", 'BUS',
"ANRUFPFLICHTIG" 'ANRUFPFLICHTIG',
], ],
"type": "ST" type: 'ST',
}; };
const stop = parse(ctx, input); const stop = parse(ctx, input);
t.same(stop, { t.same(stop, {
@ -110,25 +109,25 @@ tap.test('parses a stop correctly', (t) => {
longitude: 11.85039, longitude: 11.85039,
}, },
products: { products: {
"nationalExpress": false, nationalExpress: false,
"national": false, national: false,
"regional": true, regional: true,
"bus": true, bus: true,
"taxi": true taxi: true,
} },
}); });
t.end(); t.end();
}); });
tap.test('falls back to coordinates from `lid', (t) => { tap.test('falls back to coordinates from `lid', (t) => {
const input = { const input = {
"id": "A=1@O=Bahnhof, Rothenburg ob der Tauber@X=10190711@Y=49377180@U=80@L=683407@", id: 'A=1@O=Bahnhof, Rothenburg ob der Tauber@X=10190711@Y=49377180@U=80@L=683407@',
"name": "Bahnhof, Rothenburg ob der Tauber", name: 'Bahnhof, Rothenburg ob der Tauber',
"bahnhofsInfoId": "5393", bahnhofsInfoId: '5393',
"extId": "683407", extId: '683407',
"adminID": "vgn063", adminID: 'vgn063',
"kategorie": "Bus", kategorie: 'Bus',
"nummer": "2524" nummer: '2524',
}; };
const stop = parse(ctx, input); const stop = parse(ctx, input);
@ -141,7 +140,7 @@ tap.test('falls back to coordinates from `lid', (t) => {
id: '683407', id: '683407',
latitude: 49.377180, latitude: 49.377180,
longitude: 10.190711, longitude: 10.190711,
} },
}); });
t.end(); t.end();
}); });

View file

@ -9,13 +9,13 @@ const ctx = {
tap.test('parses an operator correctly', (t) => { tap.test('parses an operator correctly', (t) => {
const op = [{ const op = [{
"kategorie": "BEFÖRDERER", kategorie: 'BEFÖRDERER',
"key": "BEF", key: 'BEF',
"value": "DB Fernverkehr AG" value: 'DB Fernverkehr AG',
},{ }, {
"kategorie": "FAHRRADMITNAHME", kategorie: 'FAHRRADMITNAHME',
"key": "FR", key: 'FR',
"value": "Bicycles conveyed - subject to reservation" value: 'Bicycles conveyed - subject to reservation',
}]; }];
t.same(parse(ctx, op), { t.same(parse(ctx, op), {
@ -29,9 +29,9 @@ tap.test('parses an operator correctly', (t) => {
tap.test('parses nothing', (t) => { tap.test('parses nothing', (t) => {
const op = [{ const op = [{
"kategorie": "INFORMATION", kategorie: 'INFORMATION',
"key": "cB", key: 'cB',
"value": "Tel. 0981-9714925, Anmeldung bis 90 Min. vor Abfahrt (Mo-So: 9-15 Uhr)" value: 'Tel. 0981-9714925, Anmeldung bis 90 Min. vor Abfahrt (Mo-So: 9-15 Uhr)',
}]; }];
t.same(parse(ctx, op), null); t.same(parse(ctx, op), null);

View file

@ -1,6 +1,6 @@
import tap from 'tap'; import tap from 'tap';
import {parseRemarks as parse} from '../../parse/remarks.js'; import {parseRemarks as parse} from '../../parse/remarks.js';
import { parseDateTime } from '../../parse/date-time.js'; import {parseDateTime} from '../../parse/date-time.js';
const ctx = { const ctx = {
data: {}, data: {},
@ -16,30 +16,30 @@ const ctx = {
tap.test('parses meldungenAsObject correctly', (t) => { tap.test('parses meldungenAsObject correctly', (t) => {
const input = {meldungenAsObject: [{ const input = {meldungenAsObject: [{
"code": "MDA-AK-MSG-1000", code: 'MDA-AK-MSG-1000',
"nachrichtKurz": "Connection is in the past.", nachrichtKurz: 'Connection is in the past.',
"nachrichtLang": "Selected connection is in the past.", nachrichtLang: 'Selected connection is in the past.',
"fahrtRichtungKennzeichen": "HINFAHRT" fahrtRichtungKennzeichen: 'HINFAHRT',
}]}; }]};
const expected = [{ const expected = [{
"code": "MDA-AK-MSG-1000", code: 'MDA-AK-MSG-1000',
"summary": "Connection is in the past.", summary: 'Connection is in the past.',
"text": "Selected connection is in the past.", text: 'Selected connection is in the past.',
"type": "hint" type: 'hint',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
}); });
tap.test('parses risNotizen correctly', (t) => { tap.test('parses risNotizen correctly', (t) => {
const input = {risNotizen: [{key: "FT", value: "Staff delayed due to earlier journey", routeIdxFrom: 0, routeIdxTo: 12}]}; const input = {risNotizen: [{key: 'FT', value: 'Staff delayed due to earlier journey', routeIdxFrom: 0, routeIdxTo: 12}]};
const expected = [{ const expected = [{
"code": "FT", code: 'FT',
"summary": "Staff delayed due to earlier journey", summary: 'Staff delayed due to earlier journey',
"text": "Staff delayed due to earlier journey", text: 'Staff delayed due to earlier journey',
"type": "warning" type: 'warning',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
@ -47,18 +47,18 @@ tap.test('parses risNotizen correctly', (t) => {
tap.test('parses low Prio him himMeldungen correctly', (t) => { tap.test('parses low Prio him himMeldungen correctly', (t) => {
const input = {himMeldungen: [{ const input = {himMeldungen: [{
"ueberschrift": "Construction work.", ueberschrift: 'Construction work.',
"text": "Advance notice! In the period from 15.12.24 to 17.01.25, construction work will take place", text: 'Advance notice! In the period from 15.12.24 to 17.01.25, construction work will take place',
"prioritaet": "NIEDRIG", prioritaet: 'NIEDRIG',
"modDateTime": "2024-12-03T12:52:29" modDateTime: '2024-12-03T12:52:29',
}]}; }]};
const expected = [{ const expected = [{
"code": undefined, code: undefined,
"summary": "Construction work.", summary: 'Construction work.',
"text": "Advance notice! In the period from 15.12.24 to 17.01.25, construction work will take place", text: 'Advance notice! In the period from 15.12.24 to 17.01.25, construction work will take place',
"type": "status", type: 'status',
"modified": "2024-12-03T12:52:29+01:00" modified: '2024-12-03T12:52:29+01:00',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
@ -66,18 +66,18 @@ tap.test('parses low Prio him himMeldungen correctly', (t) => {
tap.test('parses high Prio him himMeldungen correctly', (t) => { tap.test('parses high Prio him himMeldungen correctly', (t) => {
const input = {himMeldungen: [{ const input = {himMeldungen: [{
"ueberschrift": "Disruption.", ueberschrift: 'Disruption.',
"text": "Switch repairs between Frankfurt(Main)Hbf and Mannheim Hbf delays rail transport.", text: 'Switch repairs between Frankfurt(Main)Hbf and Mannheim Hbf delays rail transport.',
"prioritaet": "HOCH", prioritaet: 'HOCH',
"modDateTime": "2024-12-05T19:01:48" modDateTime: '2024-12-05T19:01:48',
}]}; }]};
const expected = [{ const expected = [{
"code": undefined, code: undefined,
"summary": "Disruption.", summary: 'Disruption.',
"text": "Switch repairs between Frankfurt(Main)Hbf and Mannheim Hbf delays rail transport.", text: 'Switch repairs between Frankfurt(Main)Hbf and Mannheim Hbf delays rail transport.',
"type": "warning", type: 'warning',
"modified": "2024-12-05T19:01:48+01:00" modified: '2024-12-05T19:01:48+01:00',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
@ -85,35 +85,35 @@ tap.test('parses high Prio him himMeldungen correctly', (t) => {
tap.test('parses zugattribute correctly', (t) => { tap.test('parses zugattribute correctly', (t) => {
const input = {verkehrsmittel: {zugattribute: [{ const input = {verkehrsmittel: {zugattribute: [{
"kategorie": "BEFÖRDERER", kategorie: 'BEFÖRDERER',
"key": "BEF", key: 'BEF',
"value": "DB Fernverkehr AG" value: 'DB Fernverkehr AG',
},{ }, {
"kategorie": "FAHRRADMITNAHME", kategorie: 'FAHRRADMITNAHME',
"key": "FR", key: 'FR',
"value": "Bicycles conveyed - subject to reservation", value: 'Bicycles conveyed - subject to reservation',
"teilstreckenHinweis": "(Mainz Hbf - Mannheim Hbf)" teilstreckenHinweis: '(Mainz Hbf - Mannheim Hbf)',
}, { }, {
"kategorie": "INFORMATION", kategorie: 'INFORMATION',
"key": "CK", key: 'CK',
"value": "Komfort Check-in possible (visit bahn.de/kci for more information)", value: 'Komfort Check-in possible (visit bahn.de/kci for more information)',
"teilstreckenHinweis": "(Mainz Hbf - Mannheim Hbf)" teilstreckenHinweis: '(Mainz Hbf - Mannheim Hbf)',
}]}}; }]}};
const expected = [{ const expected = [{
//"code": "bicycle-conveyance-reservation", // "code": "bicycle-conveyance-reservation",
"code": "FR", code: 'FR',
//"summary": "bicycles conveyed, subject to reservation", // "summary": "bicycles conveyed, subject to reservation",
"summary": "Bicycles conveyed - subject to reservation", summary: 'Bicycles conveyed - subject to reservation',
"text": "Bicycles conveyed - subject to reservation", text: 'Bicycles conveyed - subject to reservation',
"type": "hint", type: 'hint',
},{ }, {
//"code": "komfort-checkin", // "code": "komfort-checkin",
"code": "CK", code: 'CK',
//"summary": "Komfort-Checkin available", // "summary": "Komfort-Checkin available",
"summary": "Komfort Check-in possible (visit bahn.de/kci for more information)", summary: 'Komfort Check-in possible (visit bahn.de/kci for more information)',
"text": "Komfort Check-in possible (visit bahn.de/kci for more information)", text: 'Komfort Check-in possible (visit bahn.de/kci for more information)',
"type": "hint", type: 'hint',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
@ -121,25 +121,25 @@ tap.test('parses zugattribute correctly', (t) => {
tap.test('parses board disruptions correctly', (t) => { tap.test('parses board disruptions correctly', (t) => {
const input = { disruptions: [ const input = {disruptions: [
{ {
"disruptionID": "9aee61d6-700e-3c19-aaa0-019f5612df4c", disruptionID: '9aee61d6-700e-3c19-aaa0-019f5612df4c',
"disruptionCommunicationID": null, disruptionCommunicationID: null,
"displayPriority": 25, displayPriority: 25,
"descriptions": { descriptions: {
"DE": { DE: {
"text": "Eine Reparatur an einem Signal verzögert den Zugverkehr", text: 'Eine Reparatur an einem Signal verzögert den Zugverkehr',
"textShort": "Verzögerungen durch Reparatur an einem Signal" textShort: 'Verzögerungen durch Reparatur an einem Signal',
} },
} },
} },
]}; ]};
const expected = [{ const expected = [{
"code": undefined, code: undefined,
"summary": "Verzögerungen durch Reparatur an einem Signal", summary: 'Verzögerungen durch Reparatur an einem Signal',
"text": "Eine Reparatur an einem Signal verzögert den Zugverkehr", text: 'Eine Reparatur an einem Signal verzögert den Zugverkehr',
"type": "warning", type: 'warning',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
@ -147,46 +147,46 @@ tap.test('parses board disruptions correctly', (t) => {
tap.test('parses board messages correctly', (t) => { tap.test('parses board messages correctly', (t) => {
const input = { messages: [ const input = {messages: [
{ {
"code": "80", code: '80',
"type": "QUALITY_VARIATION", type: 'QUALITY_VARIATION',
"displayPriority": null, displayPriority: null,
"category": null, category: null,
"text": "Andere Reihenfolge der Wagen", text: 'Andere Reihenfolge der Wagen',
"textShort": null textShort: null,
} },
]}; ]};
const expected = [{ const expected = [{
"code": 80, code: 80,
"summary": "Andere Reihenfolge der Wagen", summary: 'Andere Reihenfolge der Wagen',
"text": "Andere Reihenfolge der Wagen", text: 'Andere Reihenfolge der Wagen',
"type": "status", type: 'status',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
}); });
tap.test('parses ris attributes correctly', (t) => { tap.test('parses ris attributes correctly', (t) => {
const input = { attributes: [ const input = {attributes: [
{ {
"displayPriority": null, displayPriority: null,
"displayPriorityDetail": null, displayPriorityDetail: null,
"code": "CK", code: 'CK',
"text": "Komfort Check-in verfügbar - wenn möglich bitte einchecken", text: 'Komfort Check-in verfügbar - wenn möglich bitte einchecken',
"textShort": null textShort: null,
} },
]}; ]};
const expected = [{ const expected = [{
//"code": "komfort-checkin", // "code": "komfort-checkin",
"code": "CK", code: 'CK',
//"summary": "Komfort-Checkin available", // "summary": "Komfort-Checkin available",
"summary": "Komfort Check-in verfügbar - wenn möglich bitte einchecken", summary: 'Komfort Check-in verfügbar - wenn möglich bitte einchecken',
"text": "Komfort Check-in verfügbar - wenn möglich bitte einchecken", text: 'Komfort Check-in verfügbar - wenn möglich bitte einchecken',
"type": "hint", type: 'hint',
}]; }];
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
}); });

View file

@ -1,6 +1,6 @@
import tap from 'tap'; import tap from 'tap';
import {parseWhen as parse} from '../../parse/when.js'; import {parseWhen as parse} from '../../parse/when.js';
import { parseDateTime } from '../../parse/date-time.js'; import {parseDateTime} from '../../parse/date-time.js';
const profile = { const profile = {
parseDateTime: parseDateTime, parseDateTime: parseDateTime,
@ -15,7 +15,7 @@ const ctx = {
tap.test('parseWhen works correctly', (t) => { tap.test('parseWhen works correctly', (t) => {
const date = null; const date = null;
const timeS = '2019-06-06T16:30:00'; const timeS = '2019-06-06T16:30:00';
const timeR = '2019-06-06T16:31:00'; const timeR = '2019-06-06T16:31:00';
const expected = { const expected = {
when: '2019-06-06T16:31:00+02:00', when: '2019-06-06T16:31:00+02:00',