dbnav boards, fixes

This commit is contained in:
Traines 2025-01-02 14:00:45 +00:00
parent 6538f814aa
commit 3d998de41c
19 changed files with 434 additions and 97 deletions

View file

@ -20,7 +20,7 @@ import {parseLine} from '../parse/line.js';
import {parseLocation} from '../parse/location.js'; import {parseLocation} from '../parse/location.js';
import {parsePolyline} from '../parse/polyline.js'; import {parsePolyline} from '../parse/polyline.js';
import {parseOperator} from '../parse/operator.js'; import {parseOperator} from '../parse/operator.js';
import {parseRemarks} from '../parse/remarks.js'; import {parseRemarks, parseCancelled} from '../parse/remarks.js';
import {parseStopover} from '../parse/stopover.js'; import {parseStopover} from '../parse/stopover.js';
import {parseLoadFactor, parseArrOrDepWithLoadFactor} from '../parse/load-factor.js'; import {parseLoadFactor, parseArrOrDepWithLoadFactor} from '../parse/load-factor.js';
import {parseHintByCode} from '../parse/hints-by-code.js'; import {parseHintByCode} from '../parse/hints-by-code.js';
@ -31,7 +31,7 @@ import {formatDate} from '../format/date.js';
import {formatProductsFilter} from '../format/products-filter.js'; import {formatProductsFilter} from '../format/products-filter.js';
import {formatPoi} from '../format/poi.js'; import {formatPoi} from '../format/poi.js';
import {formatStation} from '../format/station.js'; import {formatStation} from '../format/station.js';
import {formatTime} from '../format/time.js'; import {formatTime, formatTimeOfDay} from '../format/time.js';
import {formatLocation} from '../format/location.js'; import {formatLocation} from '../format/location.js';
import {formatLoyaltyCard} from '../format/loyalty-cards.js'; import {formatLoyaltyCard} from '../format/loyalty-cards.js';
@ -54,7 +54,7 @@ const defaultProfile = {
ageGroup, ageGroupFromAge, ageGroupLabel, ageGroup, ageGroupFromAge, ageGroupLabel,
transformReqBody: id, transformReqBody: id,
transformReq: id, transformReq: id,
randomizeUserAgent: true, randomizeUserAgent: false,
logRequest, logRequest,
logResponse, logResponse,
@ -82,6 +82,7 @@ const defaultProfile = {
parsePolyline, parsePolyline,
parseOperator, parseOperator,
parseRemarks, parseRemarks,
parseCancelled,
parseStopover, parseStopover,
parseLoadFactor, parseLoadFactor,
parseArrOrDepWithLoadFactor, parseArrOrDepWithLoadFactor,
@ -99,6 +100,7 @@ const defaultProfile = {
formatProductsFilter, formatProductsFilter,
formatStation, formatStation,
formatTime, formatTime,
formatTimeOfDay,
formatTravellers: notImplemented, formatTravellers: notImplemented,
formatRectangle: id, formatRectangle: id,

View file

@ -7,7 +7,9 @@ const products = [
short: 'ICE', short: 'ICE',
vendo: 'ICE', vendo: 'ICE',
ris: 'HIGH_SPEED_TRAIN', ris: 'HIGH_SPEED_TRAIN',
ris_alt: 'HIGH_SPEED_TRAIN',
dbnav: 'HOCHGESCHWINDIGKEITSZUEGE', dbnav: 'HOCHGESCHWINDIGKEITSZUEGE',
dbnav_short: 'ICE',
default: true, default: true,
}, },
{ {
@ -18,7 +20,9 @@ const products = [
short: 'IC/EC', short: 'IC/EC',
vendo: 'EC_IC', vendo: 'EC_IC',
ris: 'INTERCITY_TRAIN', ris: 'INTERCITY_TRAIN',
ris_alt: 'INTERCITY_TRAIN',
dbnav: 'INTERCITYUNDEUROCITYZUEGE', dbnav: 'INTERCITYUNDEUROCITYZUEGE',
dbnav_short: 'IC_EC',
default: true, default: true,
}, },
{ {
@ -29,7 +33,9 @@ const products = [
short: 'RE/IR', short: 'RE/IR',
vendo: 'IR', vendo: 'IR',
ris: 'INTER_REGIONAL_TRAIN', ris: 'INTER_REGIONAL_TRAIN',
ris_alt: 'INTER_REGIONAL_TRAIN',
dbnav: 'INTERREGIOUNDSCHNELLZUEGE', dbnav: 'INTERREGIOUNDSCHNELLZUEGE',
dbnav_short: 'IR',
default: true, default: true,
}, },
{ {
@ -40,7 +46,9 @@ const products = [
short: 'RB', short: 'RB',
vendo: 'REGIONAL', vendo: 'REGIONAL',
ris: 'REGIONAL_TRAIN', ris: 'REGIONAL_TRAIN',
ris_alt: 'REGIONAL_TRAIN',
dbnav: 'NAHVERKEHRSONSTIGEZUEGE', dbnav: 'NAHVERKEHRSONSTIGEZUEGE',
dbnav_short: 'RB',
default: true, default: true,
}, },
{ {
@ -51,7 +59,9 @@ const products = [
short: 'S', short: 'S',
vendo: 'SBAHN', vendo: 'SBAHN',
ris: 'CITY_TRAIN', ris: 'CITY_TRAIN',
ris_alt: 'CITY_TRAIN',
dbnav: 'SBAHNEN', dbnav: 'SBAHNEN',
dbnav_short: 'SBAHN',
default: true, default: true,
}, },
{ {
@ -62,7 +72,9 @@ const products = [
short: 'B', short: 'B',
vendo: 'BUS', vendo: 'BUS',
ris: 'BUS', ris: 'BUS',
ris_alt: 'BUS',
dbnav: 'BUSSE', dbnav: 'BUSSE',
dbnav_short: 'BUS',
default: true, default: true,
}, },
{ {
@ -73,7 +85,9 @@ const products = [
short: 'F', short: 'F',
vendo: 'SCHIFF', vendo: 'SCHIFF',
ris: 'FERRY', ris: 'FERRY',
ris_alt: 'FERRY',
dbnav: 'SCHIFFE', dbnav: 'SCHIFFE',
dbnav_short: 'SCHIFF',
default: true, default: true,
}, },
{ {
@ -84,7 +98,9 @@ const products = [
short: 'U', short: 'U',
vendo: 'UBAHN', vendo: 'UBAHN',
ris: 'SUBWAY', ris: 'SUBWAY',
ris_alt: 'SUBWAY',
dbnav: 'UBAHN', dbnav: 'UBAHN',
dbnav_short: 'UBAHN',
default: true, default: true,
}, },
{ {
@ -95,7 +111,9 @@ const products = [
short: 'T', short: 'T',
vendo: 'TRAM', vendo: 'TRAM',
ris: 'TRAM', ris: 'TRAM',
ris_alt: 'TRAM',
dbnav: 'STRASSENBAHN', dbnav: 'STRASSENBAHN',
dbnav_short: 'STR',
default: true, default: true,
}, },
{ {
@ -106,7 +124,9 @@ const products = [
short: 'Taxi', short: 'Taxi',
vendo: 'ANRUFPFLICHTIG', vendo: 'ANRUFPFLICHTIG',
ris: 'TAXI', ris: 'TAXI',
ris_alt: 'SHUTTLE',
dbnav: 'ANRUFPFLICHTIGEVERKEHRE', dbnav: 'ANRUFPFLICHTIGEVERKEHRE',
dbnav_short: 'ANRUFPFLICHTIGEVERKEHRE',
default: true, default: true,
}, },
]; ];

View file

@ -5,6 +5,6 @@
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search", "locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",
"nearbyEndpoint": "https://app.vendo.noncd.db.de/mob/location/nearby", "nearbyEndpoint": "https://app.vendo.noncd.db.de/mob/location/nearby",
"tripEndpoint": "https://app.vendo.noncd.db.de/mob/zuglauf", "tripEndpoint": "https://app.vendo.noncd.db.de/mob/zuglauf",
"boardEndpoint": "https://app.vendo.noncd.db.de/mob/bahnhofstafel/abfahrt", "boardEndpoint": "https://app.vendo.noncd.db.de/mob/bahnhofstafel/",
"defaultLanguage": "en" "defaultLanguage": "en"
} }

View file

@ -8,7 +8,7 @@ const formatStationBoardReq = (ctx, station, type) => {
path: type == 'departures' ? 'abfahrt' : 'ankunft', path: type == 'departures' ? 'abfahrt' : 'ankunft',
body: {anfragezeit: profile.formatTimeOfDay(profile, opt.when), datum: profile.formatDate(profile, opt.when), ursprungsBahnhofId: profile.formatStation(station).lid, verkehrsmittel: profile.formatProductsFilter(ctx, opt.products || {}, 'dbnav')}, body: {anfragezeit: profile.formatTimeOfDay(profile, opt.when), datum: profile.formatDate(profile, opt.when), ursprungsBahnhofId: profile.formatStation(station).lid, verkehrsmittel: profile.formatProductsFilter(ctx, opt.products || {}, 'dbnav')},
method: 'POST', method: 'POST',
header: getHeaders('application/x.db.vendo.mob.bahnhofstafeln.v2+json'), headers: getHeaders('application/x.db.vendo.mob.bahnhofstafeln.v2+json'),
}; };
}; };

View file

@ -8,15 +8,20 @@ const createParseArrOrDep = (prefix) => {
const parseArrOrDep = (ctx, d) => { // d = raw arrival/departure const parseArrOrDep = (ctx, d) => { // d = raw arrival/departure
const {profile, opt} = ctx; const {profile, opt} = ctx;
const cancelled = profile.parseCancelled(d);
const res = { const res = {
tripId: d.journeyID || d.train.journeyId, tripId: d.journeyID || d.train?.journeyId || d.zuglaufId,
stop: profile.parseLocation(ctx, d.station), stop: profile.parseLocation(ctx, d.station || d.abfrageOrt),
...profile.parseWhen(ctx, null, d.timeSchedule ? d.timeSchedule : d.time, d.timeType != 'SCHEDULE' ? d.timePredicted ? d.timePredicted : d.time : null, d.canceled), ...profile.parseWhen(
...profile.parsePlatform(ctx, d.platformSchedule ? d.platformSchedule : d.platform, d.platformPredicted ? d.platformPredicted : d.platform, d.canceled), ctx,
null,
d.timeSchedule || d.time || d.abgangsDatum || d.ankunftsDatum,
d.timeType != 'SCHEDULE' ? d.timePredicted || d.time || d.ezAbgangsDatum || d.ezAnkunftsDatum : null,
cancelled),
...profile.parsePlatform(ctx, d.platformSchedule || d.platform || d.gleis, d.platformPredicted || d.platform || d.ezGleis, cancelled),
// prognosisType: TODO // prognosisType: TODO
direction: d.transport?.direction?.stopPlaces?.length > 0 && profile.parseStationName(ctx, d.transport?.direction?.stopPlaces[0].name) || profile.parseStationName(ctx, d.destination?.name) || null, direction: d.transport?.direction?.stopPlaces?.length > 0 && profile.parseStationName(ctx, d.transport?.direction?.stopPlaces[0].name) || profile.parseStationName(ctx, d.destination?.name || d.richtung) || null,
provenance: profile.parseStationName(ctx, d.transport?.origin?.name) || profile.parseStationName(ctx, d.origin?.name) || null, provenance: profile.parseStationName(ctx, d.transport?.origin?.name || d.origin?.name || d.abgangsOrt?.name) || null,
line: profile.parseLine(ctx, d) || null, line: profile.parseLine(ctx, d) || null,
remarks: [], remarks: [],
origin: profile.parseLocation(ctx, d.transport?.origin || d.origin) || null, origin: profile.parseLocation(ctx, d.transport?.origin || d.origin) || null,
@ -26,7 +31,7 @@ const createParseArrOrDep = (prefix) => {
// TODO pos // TODO pos
if (d.canceled) { if (cancelled) {
res.cancelled = true; res.cancelled = true;
Object.defineProperty(res, 'canceled', {value: true}); Object.defineProperty(res, 'canceled', {value: true});
} }

View file

@ -1,5 +1,3 @@
import {parseRemarks, isStopCancelled} from './remarks.js';
const locationFallback = (id, name, fallbackLocations) => { const locationFallback = (id, name, fallbackLocations) => {
if (fallbackLocations && (id && fallbackLocations[id] || name && fallbackLocations[name])) { if (fallbackLocations && (id && fallbackLocations[id] || name && fallbackLocations[name])) {
return fallbackLocations[id] || fallbackLocations[name]; return fallbackLocations[id] || fallbackLocations[name];
@ -20,7 +18,7 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
destination: pt.halte?.length > 0 ? profile.parseLocation(ctx, pt.halte[pt.halte.length - 1]) : locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt, fallbackLocations), destination: pt.halte?.length > 0 ? profile.parseLocation(ctx, pt.halte[pt.halte.length - 1]) : locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt, fallbackLocations),
}; };
const cancelledDep = pt.halte?.length > 0 && isStopCancelled(pt.halte[0]); const cancelledDep = pt.halte?.length > 0 && profile.parseCancelled(pt.halte[0]);
const dep = profile.parseWhen(ctx, date, pt.abfahrtsZeitpunkt, pt.ezAbfahrtsZeitpunkt, cancelledDep); const dep = profile.parseWhen(ctx, date, pt.abfahrtsZeitpunkt, pt.ezAbfahrtsZeitpunkt, cancelledDep);
res.departure = dep.when; res.departure = dep.when;
res.plannedDeparture = dep.plannedWhen; res.plannedDeparture = dep.plannedWhen;
@ -29,7 +27,7 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // 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 && profile.parseCancelled(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;
@ -84,7 +82,7 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
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 = profile.parseRemarks(ctx, pt);
} }
} }

View file

@ -5,19 +5,19 @@ const parseLine = (ctx, p) => {
const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no; const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no;
const res = { const res = {
type: 'line', type: 'line',
id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription || p.train?.no), // TODO terrible id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription || p.train && p.train.category + ' ' + p.train.lineName + ' ' + p.train.no || p.langtext || p.mitteltext), // TODO terrible
fahrtNr: fahrtNr ? String(fahrtNr) : undefined, fahrtNr: fahrtNr ? String(fahrtNr) : undefined, // TODO extract from zuglaufId?
name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription || p.train && p.train.category + ' ' + p.train.lineName, name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription || p.train && p.train.category + ' ' + p.train.lineName || p.langtext || p.mitteltext,
public: true, public: true,
}; };
// TODO res.adminCode // TODO res.adminCode
res.productName = p.verkehrsmittel?.kurzText || p.transport?.category || p.train?.category; res.productName = p.verkehrsmittel?.kurzText || p.transport?.category || p.train?.category || p.kurztext;
const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type); const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type || pp.ris_alt == p.train?.type || pp.dbnav_short == p.produktGattung);
res.mode = foundProduct?.mode; res.mode = foundProduct?.mode;
res.product = foundProduct?.id; res.product = foundProduct?.id;
res.operator = profile.parseOperator(ctx, p.verkehrsmittel?.zugattribute || p.zugattribute); res.operator = profile.parseOperator(ctx, p.verkehrsmittel?.zugattribute || p.zugattribute); // TODO regio-guide op
return res; return res;
}; };

View file

@ -28,15 +28,15 @@ const parseLocation = (ctx, l) => {
res.longitude = lid.X / 1000000; res.longitude = lid.X / 1000000;
} }
if (l.type === STATION || l.extId || l.evaNumber || l.evaNo || lid.A == 1) { if (l.type === STATION || l.extId || l.evaNumber || l.evaNo || l.evaNr || lid.A == '1') {
let stop = { let stop = {
type: 'station', type: 'station',
id: res.id, id: res.id,
name: name, name: name,
location: 'number' === typeof res.latitude
? res
: null, // todo: remove `.id`
}; };
if ('number' === typeof res.latitude) {
stop.location = res; // todo: remove `.id`
}
// TODO subStops // TODO subStops
if ('products' in l) { if ('products' in l) {
@ -57,10 +57,10 @@ const parseLocation = (ctx, l) => {
} }
res.name = name; res.name = name;
if (l.type === ADDRESS || lid.A == 2) { if (l.type === ADDRESS || lid.A == '2') {
res.address = name; res.address = name;
} }
if (l.type === POI || lid.A == 4) { if (l.type === POI || lid.A == '4') {
res.poi = true; res.poi = true;
} }

View file

@ -4,17 +4,17 @@ const parseOperator = (ctx, zugattrib) => {
if (!zugattrib) { if (!zugattrib) {
return null; return null;
} }
const bef = zugattrib.find(z => z.key == 'BEF'); const bef = zugattrib.find(z => z.key == 'BEF' || z.key == 'OP');
if (!bef) { if (!bef) {
return null; return null;
} }
const name = bef.value && bef.value.trim(); const name = bef.value || bef.text;
if (!name) { if (!name) {
return null; return null;
} }
return { return {
type: 'operator', type: 'operator',
id: slugg(name), // todo: find a more reliable way id: slugg(name.trim()), // todo: find a more reliable way
name, name,
}; };
}; };

View file

@ -3,44 +3,53 @@ 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.himMeldungen || [],
ref.meldungenAsObject || [],
ref.verkehrsmittel?.zugattribute || [],
ref.messages || [],
ref.attributes || [],
ref.disruptions || [], ref.disruptions || [],
ref.risNotizen || [],
ref.echtzeitNotizen && ref.echtzeitNotizen.map(e => {
e.prio = 'HOCH'; return e;
}) || [],
ref.himMeldungen || [],
ref.himNotizen || [],
ref.serviceNotiz && [ref.serviceNotiz] || [],
ref.messages || [],
ref.meldungenAsObject || [],
ref.attributNotizen || [],
ref.attributes || [],
ref.verkehrsmittel?.zugattribute || [],
]) ])
.map(remark => { .map(remark => {
if (remark.kategorie) { if (remark.kategorie || remark.priority) {
const res = ctx.profile.parseHintByCode(remark); const res = ctx.profile.parseHintByCode(remark);
if (res) { if (res) {
return res; return res;
} }
} }
let type = 'hint'; let type = 'hint';
if (remark.prioritaet || remark.type) { if (remark.prioritaet || remark.prio || remark.type) {
type = 'status'; type = 'status';
} }
if (!remark.kategorie && remark.key || remark.disruptionID || remark.prioritaet && remark.prioritaet == 'HOCH') { if (!remark.priority && !remark.kategorie && remark.key || remark.disruptionID
|| remark.prioritaet && remark.prioritaet == 'HOCH' || remark.prio && remark.prio == 'HOCH' || remark.priority && remark.priority < 100) {
type = 'warning'; type = 'warning';
} }
let res = { let res = {
code: remark.code || remark.key, code: remark.code || remark.key,
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || Object.values(remark.descriptions || {}) summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text
|| Object.values(remark.descriptions || {})
.shift()?.textShort, .shift()?.textShort,
text: remark.nachrichtLang || remark.value || remark.text || Object.values(remark.descriptions || {}) text: remark.nachrichtLang || remark.value || remark.text
|| Object.values(remark.descriptions || {})
.shift()?.text, .shift()?.text,
type: type, type: type,
}; };
if (remark.modDateTime) { if (remark.modDateTime || remark.letzteAktualisierung) {
res.modified = ctx.profile.parseDateTime(ctx, null, remark.modDateTime); res.modified = ctx.profile.parseDateTime(ctx, null, remark.modDateTime || remark.letzteAktualisierung);
} }
// TODO fromStops, toStops = routeIdxFrom ?? // TODO fromStops, toStops = routeIdxFrom ??
// TODO prio // TODO prio
return res; return res;
}) })
.filter(remark => remark.code != 'BEF'); .filter(remark => remark.code != 'BEF' && remark.code != 'OP');
}; };
/* /*
@ -189,11 +198,15 @@ const parseRemarks = (ctx, ref) => {
] ]
*/ */
const isStopCancelled = (ref) => { const parseCancelled = (ref) => {
return Boolean(ref.risNotizen.find(r => r.key == 'text.realtime.stop.cancelled' || r.type == 'HALT_AUSFALL')); return ref.canceled || ref.cancelled || (ref.risNotizen || ref.echtzeitNotizen) && Boolean((ref.risNotizen || ref.echtzeitNotizen).find(r => r.key == 'text.realtime.stop.cancelled'
|| r.type == 'HALT_AUSFALL'
|| r.text == 'Halt entfällt'
|| r.text == 'Stop cancelled',
));
}; };
export { export {
parseRemarks, parseRemarks,
isStopCancelled, parseCancelled,
}; };

View file

@ -1,9 +1,7 @@
import {parseRemarks, isStopCancelled} from './remarks.js';
const parseStopover = (ctx, st, date) => { // st = raw stopover const parseStopover = (ctx, st, date) => { // st = raw stopover
const {profile, opt} = ctx; const {profile, opt} = ctx;
const cancelled = isStopCancelled(st); const cancelled = profile.parseCancelled(st);
const arr = profile.parseWhen(ctx, date, st.ankunftsZeitpunkt, st.ezAnkunftsZeitpunkt, cancelled); const arr = profile.parseWhen(ctx, date, st.ankunftsZeitpunkt, st.ezAnkunftsZeitpunkt, cancelled);
const arrPl = profile.parsePlatform(ctx, st.gleis, st.ezGleis); const arrPl = profile.parsePlatform(ctx, st.gleis, st.ezGleis);
const dep = profile.parseWhen(ctx, date, st.abfahrtsZeitpunkt, st.ezAbfahrtsZeitpunkt, cancelled); const dep = profile.parseWhen(ctx, date, st.abfahrtsZeitpunkt, st.ezAbfahrtsZeitpunkt, cancelled);
@ -51,7 +49,7 @@ const parseStopover = (ctx, st, date) => { // st = raw stopover
// TODO res.additional = true; // TODO res.additional = true;
if (opt.remarks) { if (opt.remarks) {
res.remarks = parseRemarks(ctx, st); res.remarks = profile.parseRemarks(ctx, st);
} }
return res; return res;

32
test/dbnav-departures.js Normal file
View file

@ -0,0 +1,32 @@
// 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 tap from 'tap';
import {createClient} from '../index.js';
import {profile as rawProfile} from '../p/db/index.js';
const res = require('./fixtures/dbnav-departures.json');
import {dbnavDepartures as expected} from './fixtures/dbnav-departures.js';
const client = createClient(rawProfile, 'public-transport/hafas-client:test');
const {profile} = client;
const opt = {
direction: null,
duration: 10,
linesOfStops: true,
remarks: true,
stopovers: true,
includeRelatedStations: true,
when: '2019-08-19T20:30:00+02:00',
products: {},
};
tap.test('parses a regio-guide departure correctly', (t) => {
const ctx = {profile, opt, common: null, res};
const departures = res.bahnhofstafelAbfahrtPositionen.map(d => profile.parseDeparture(ctx, d));
t.same(departures, expected);
t.end();
});

View file

@ -5,7 +5,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '8089100', id: '8089100',
name: 'Berlin Jungfernheide (S)', name: 'Berlin Jungfernheide (S)',
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',
@ -37,7 +36,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '8089118', id: '8089118',
name: 'Berlin Beusselstraße', name: 'Berlin Beusselstraße',
location: null,
}, },
destination: null, destination: null,
}, },
@ -47,7 +45,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
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',
@ -85,7 +82,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '732218', id: '732218',
name: 'Rudow (U), Berlin', name: 'Rudow (U), Berlin',
location: null,
}, },
destination: null, destination: null,
}, },
@ -95,7 +91,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
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',
@ -133,7 +128,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '730993', id: '730993',
name: 'Goerdelersteg, Berlin', name: 'Goerdelersteg, Berlin',
location: null,
}, },
destination: null, destination: null,
}, },
@ -143,7 +137,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '8089100', id: '8089100',
name: 'Berlin Jungfernheide (S)', name: 'Berlin Jungfernheide (S)',
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',
@ -175,7 +168,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '8089118', id: '8089118',
name: 'Berlin Beusselstraße', name: 'Berlin Beusselstraße',
location: null,
}, },
destination: null, destination: null,
}, },
@ -185,7 +177,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
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',
@ -223,7 +214,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '732218', id: '732218',
name: 'Rudow (U), Berlin', name: 'Rudow (U), Berlin',
location: null,
}, },
destination: null, destination: null,
}, },
@ -233,7 +223,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '8089100', id: '8089100',
name: 'Berlin Jungfernheide (S)', name: 'Berlin Jungfernheide (S)',
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',
@ -265,7 +254,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '8089118', id: '8089118',
name: 'Berlin Beusselstraße', name: 'Berlin Beusselstraße',
location: null,
}, },
destination: null, destination: null,
}, },
@ -275,7 +263,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
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',
@ -313,7 +300,6 @@ const dbArrivals = [
type: 'station', type: 'station',
id: '731176', id: '731176',
name: 'Rathaus Spandau (S+U), Berlin', name: 'Rathaus Spandau (S+U), Berlin',
location: null,
}, },
destination: null, destination: null,
}, },

View file

@ -5,7 +5,6 @@ const dbDepartures = [
type: 'station', type: 'station',
id: '8000365', id: '8000365',
name: 'Dombühl', name: 'Dombühl',
location: null,
}, },
when: '2024-12-12T12:34:00+01:00', when: '2024-12-12T12:34:00+01:00',
plannedWhen: '2024-12-12T12:34:00+01:00', plannedWhen: '2024-12-12T12:34:00+01:00',
@ -16,7 +15,7 @@ const dbDepartures = [
provenance: null, provenance: null,
line: { line: {
type: 'line', type: 'line',
id: '88617', id: 're-90-88617',
fahrtNr: '88617', fahrtNr: '88617',
name: 'RE 90', name: 'RE 90',
public: true, public: true,
@ -31,7 +30,6 @@ const dbDepartures = [
type: 'station', type: 'station',
id: '8000284', id: '8000284',
name: 'Nürnberg Hbf', name: 'Nürnberg Hbf',
location: null,
}, },
}, },
{ {
@ -40,7 +38,6 @@ const dbDepartures = [
type: 'station', type: 'station',
id: '682943', id: '682943',
name: 'Bahnhof, Dombühl', name: 'Bahnhof, Dombühl',
location: null,
}, },
when: '2024-12-12T12:50:00+01:00', when: '2024-12-12T12:50:00+01:00',
plannedWhen: '2024-12-12T12:50:00+01:00', plannedWhen: '2024-12-12T12:50:00+01:00',
@ -51,7 +48,7 @@ const dbDepartures = [
provenance: null, provenance: null,
line: { line: {
type: 'line', type: 'line',
id: '2221', id: 'bus-813-2221',
fahrtNr: '2221', fahrtNr: '2221',
name: 'Bus 813', name: 'Bus 813',
public: true, public: true,
@ -66,7 +63,6 @@ const dbDepartures = [
type: 'station', type: 'station',
id: '676542', id: '676542',
name: 'Gymnasium, Dinkelsbühl', name: 'Gymnasium, Dinkelsbühl',
location: null,
}, },
}, },
{ {
@ -75,7 +71,6 @@ const dbDepartures = [
type: 'station', type: 'station',
id: '682943', id: '682943',
name: 'Bahnhof, Dombühl', name: 'Bahnhof, Dombühl',
location: null,
}, },
when: '2024-12-12T12:50:00+01:00', when: '2024-12-12T12:50:00+01:00',
plannedWhen: '2024-12-12T12:50:00+01:00', plannedWhen: '2024-12-12T12:50:00+01:00',
@ -86,7 +81,7 @@ const dbDepartures = [
provenance: null, provenance: null,
line: { line: {
type: 'line', type: 'line',
id: '2177', id: 'bus-807-2177',
fahrtNr: '2177', fahrtNr: '2177',
name: 'Bus 807', name: 'Bus 807',
public: true, public: true,
@ -101,7 +96,6 @@ const dbDepartures = [
type: 'station', type: 'station',
id: '683407', id: '683407',
name: 'Bahnhof, Rothenburg ob der Tauber', name: 'Bahnhof, Rothenburg ob der Tauber',
location: null,
}, },
}, },
]; ];

224
test/fixtures/dbnav-departures.js vendored Normal file
View file

@ -0,0 +1,224 @@
const dbnavDepartures = [
{
tripId: '2|#VN#1#ST#1734722398#PI#1#ZI#929785#TA#1#DA#291224#1S#374704#1T#1502#LS#465246#LT#1530#PU#81#RT#1#CA#rfb#ZE#9870#ZB#RUF 9870#PC#9#FR#374704#FT#1502#TO#465246#TT#1530#',
stop: {
type: 'station',
id: '374704',
name: 'Bahnhof, Rothenburg ob der Tauber',
location: {
type: 'location',
id: '374704',
latitude: 49.377189,
longitude: 10.191107,
},
},
when: '2024-12-29T15:02:00+01:00',
plannedWhen: '2024-12-29T15:02:00+01:00',
delay: null,
platform: null,
plannedPlatform: null,
direction: 'ZOB, Creglingen',
provenance: null,
line: {
type: 'line',
id: 'ruf-9870',
name: 'RUF 9870',
fahrtNr: undefined,
public: true,
productName: 'RUF',
mode: 'taxi',
product: 'taxi',
operator: null,
},
remarks: [],
origin: null,
destination: null,
},
{
tripId: '2|#VN#1#ST#1734722398#PI#1#ZI#214076#TA#1#DA#291224#1S#8005190#1T#1505#LS#8000091#LT#1520#PU#81#RT#1#CA#RB#ZE#58904#ZB#RB 58904#PC#3#FR#8005190#FT#1505#TO#8000091#TT#1520#',
stop: {
type: 'station',
id: '8005190',
name: 'Rothenburg ob der Tauber',
location: {
type: 'location',
id: '8005190',
latitude: 49.376686,
longitude: 10.190702,
},
},
when: '2024-12-29T15:05:00+01:00',
plannedWhen: '2024-12-29T15:05:00+01:00',
delay: 0,
platform: '1',
plannedPlatform: '1',
direction: 'Steinach(bei Rothenburg ob der Tauber)',
provenance: null,
line: {
type: 'line',
id: 'rb-82',
name: 'RB 82',
fahrtNr: undefined,
public: true,
productName: 'RB',
mode: 'train',
product: 'regional',
operator: null,
},
remarks: [],
origin: null,
destination: null,
},
{
tripId: '2|#VN#1#ST#1734722398#PI#1#ZI#863886#TA#3#DA#291224#1S#682943#1T#1450#LS#683407#LT#1531#PU#81#RT#1#CA#Bus#ZE#807#ZB#Bus 807#PC#5#FR#682943#FT#1450#TO#683407#TT#1531#',
stop: {
type: 'station',
id: '680433',
name: 'Schlachthof, Rothenburg ob der Tauber',
location: {
type: 'location',
id: '680433',
latitude: 49.373989,
longitude: 10.189255,
},
},
when: '2024-12-29T15:30:00+01:00',
plannedWhen: '2024-12-29T15:30:00+01:00',
delay: null,
platform: null,
plannedPlatform: null,
direction: 'Bahnhof, Rothenburg ob der Tauber',
provenance: null,
line: {
type: 'line',
id: 'bus-807',
name: 'Bus 807',
fahrtNr: undefined,
public: true,
productName: 'Bus',
mode: 'bus',
product: 'bus',
operator: null,
},
remarks: [],
origin: null,
destination: null,
},
{
tripId: '2|#VN#1#ST#1734722398#PI#1#ZI#863865#TA#0#DA#291224#1S#683407#1T#1532#LS#682943#LT#1624#PU#81#RT#1#CA#Bus#ZE#807#ZB#Bus 807#PC#5#FR#683407#FT#1532#TO#682943#TT#1624#',
stop: {
type: 'station',
id: '683407',
name: 'Bahnhof, Rothenburg ob der Tauber',
location: {
type: 'location',
id: '683407',
latitude: 49.37718,
longitude: 10.190711,
},
},
when: '2024-12-29T15:32:00+01:00',
plannedWhen: '2024-12-29T15:32:00+01:00',
delay: null,
platform: null,
plannedPlatform: null,
direction: 'Bahnhof, Dombühl',
provenance: null,
line: {
type: 'line',
id: 'bus-807',
name: 'Bus 807',
fahrtNr: undefined,
public: true,
productName: 'Bus',
mode: 'bus',
product: 'bus',
operator: null,
},
remarks: [],
origin: null,
destination: null,
},
{
tripId: '2|#VN#1#ST#1734722398#PI#1#ZI#863865#TA#0#DA#291224#1S#683407#1T#1532#LS#682943#LT#1624#PU#81#RT#1#CA#Bus#ZE#807#ZB#Bus 807#PC#5#FR#683407#FT#1532#TO#682943#TT#1624#',
stop: {
type: 'station',
id: '680433',
name: 'Schlachthof, Rothenburg ob der Tauber',
location: {
type: 'location',
id: '680433',
latitude: 49.373989,
longitude: 10.189255,
},
},
when: '2024-12-29T15:33:00+01:00',
plannedWhen: '2024-12-29T15:33:00+01:00',
delay: null,
platform: null,
plannedPlatform: null,
direction: 'Bahnhof, Dombühl',
provenance: null,
line: {
type: 'line',
id: 'bus-807',
name: 'Bus 807',
fahrtNr: undefined,
public: true,
productName: 'Bus',
mode: 'bus',
product: 'bus',
operator: null,
},
remarks: [],
origin: null,
destination: null,
},
{
tripId: '2|#VN#1#ST#1734722398#PI#1#ZI#864022#TA#1#DA#291224#1S#677019#1T#1458#LS#683407#LT#1548#PU#81#RT#1#CA#Bus#ZE#817#ZB#Bus 817#PC#5#FR#677019#FT#1458#TO#683407#TT#1548#',
stop: {
type: 'station',
id: '680433',
name: 'Schlachthof, Rothenburg ob der Tauber',
location: {
type: 'location',
id: '680433',
latitude: 49.373989,
longitude: 10.189255,
},
},
when: null,
plannedWhen: '2024-12-29T15:47:00+01:00',
prognosedWhen: null,
delay: null,
platform: null,
plannedPlatform: null,
prognosedPlatform: null,
direction: 'Bahnhof, Rothenburg ob der Tauber',
provenance: null,
line: {
type: 'line',
id: 'bus-817',
name: 'Bus 817',
fahrtNr: undefined,
public: true,
productName: 'Bus',
mode: 'bus',
product: 'bus',
operator: null,
},
remarks: [{
code: undefined,
summary: 'Halt entfällt',
text: 'Halt entfällt',
type: 'warning',
}],
origin: null,
destination: null,
cancelled: true,
},
];
export {
dbnavDepartures,
};

1
test/fixtures/dbnav-departures.json vendored Normal file
View file

@ -0,0 +1 @@
{"bahnhofstafelAbfahrtPositionen":[{"zuglaufId":"2|#VN#1#ST#1734722398#PI#1#ZI#929785#TA#1#DA#291224#1S#374704#1T#1502#LS#465246#LT#1530#PU#81#RT#1#CA#rfb#ZE#9870#ZB#RUF 9870#PC#9#FR#374704#FT#1502#TO#465246#TT#1530#","kurztext":"RUF","mitteltext":"RUF 9870","abfrageOrt":{"name":"Bahnhof, Rothenburg ob der Tauber","locationId":"A=1@O=Bahnhof, Rothenburg ob der Tauber@X=10191107@Y=49377189@U=81@L=374704@","evaNr":"374704","stationId":"5393"},"richtung":"ZOB, Creglingen","echtzeitNotizen":[],"abgangsDatum":"2024-12-29T15:02:00+01:00","produktGattung":"ANRUFPFLICHTIGEVERKEHRE"},{"gleis":"1","zuglaufId":"2|#VN#1#ST#1734722398#PI#1#ZI#214076#TA#1#DA#291224#1S#8005190#1T#1505#LS#8000091#LT#1520#PU#81#RT#1#CA#RB#ZE#58904#ZB#RB 58904#PC#3#FR#8005190#FT#1505#TO#8000091#TT#1520#","kurztext":"RB","mitteltext":"RB 82","abfrageOrt":{"name":"Rothenburg ob der Tauber","locationId":"A=1@O=Rothenburg ob der Tauber@X=10190702@Y=49376686@U=81@L=8005190@i=U×008022698@","evaNr":"8005190","stationId":"5393"},"richtung":"Steinach(bei Rothenburg ob der Tauber)","echtzeitNotizen":[],"abgangsDatum":"2024-12-29T15:05:00+01:00","produktGattung":"RB","ezAbgangsDatum":"2024-12-29T15:05:00+01:00"},{"zuglaufId":"2|#VN#1#ST#1734722398#PI#1#ZI#863886#TA#3#DA#291224#1S#682943#1T#1450#LS#683407#LT#1531#PU#81#RT#1#CA#Bus#ZE#807#ZB#Bus 807#PC#5#FR#682943#FT#1450#TO#683407#TT#1531#","kurztext":"Bus","mitteltext":"Bus 807","abfrageOrt":{"name":"Schlachthof, Rothenburg ob der Tauber","locationId":"A=1@O=Schlachthof, Rothenburg ob der Tauber@X=10189255@Y=49373989@U=81@L=680433@","evaNr":"680433"},"richtung":"Bahnhof, Rothenburg ob der Tauber","echtzeitNotizen":[],"abgangsDatum":"2024-12-29T15:30:00+01:00","produktGattung":"BUS"},{"zuglaufId":"2|#VN#1#ST#1734722398#PI#1#ZI#863865#TA#0#DA#291224#1S#683407#1T#1532#LS#682943#LT#1624#PU#81#RT#1#CA#Bus#ZE#807#ZB#Bus 807#PC#5#FR#683407#FT#1532#TO#682943#TT#1624#","kurztext":"Bus","mitteltext":"Bus 807","abfrageOrt":{"name":"Bahnhof, Rothenburg ob der Tauber","locationId":"A=1@O=Bahnhof, Rothenburg ob der Tauber@X=10190711@Y=49377180@U=81@L=683407@","evaNr":"683407","stationId":"5393"},"richtung":"Bahnhof, Dombühl","echtzeitNotizen":[],"abgangsDatum":"2024-12-29T15:32:00+01:00","produktGattung":"BUS"},{"zuglaufId":"2|#VN#1#ST#1734722398#PI#1#ZI#863865#TA#0#DA#291224#1S#683407#1T#1532#LS#682943#LT#1624#PU#81#RT#1#CA#Bus#ZE#807#ZB#Bus 807#PC#5#FR#683407#FT#1532#TO#682943#TT#1624#","kurztext":"Bus","mitteltext":"Bus 807","abfrageOrt":{"name":"Schlachthof, Rothenburg ob der Tauber","locationId":"A=1@O=Schlachthof, Rothenburg ob der Tauber@X=10189255@Y=49373989@U=81@L=680433@","evaNr":"680433"},"richtung":"Bahnhof, Dombühl","echtzeitNotizen":[],"abgangsDatum":"2024-12-29T15:33:00+01:00","produktGattung":"BUS"},{"zuglaufId":"2|#VN#1#ST#1734722398#PI#1#ZI#864022#TA#1#DA#291224#1S#677019#1T#1458#LS#683407#LT#1548#PU#81#RT#1#CA#Bus#ZE#817#ZB#Bus 817#PC#5#FR#677019#FT#1458#TO#683407#TT#1548#","kurztext":"Bus","mitteltext":"Bus 817","abfrageOrt":{"name":"Schlachthof, Rothenburg ob der Tauber","locationId":"A=1@O=Schlachthof, Rothenburg ob der Tauber@X=10189255@Y=49373989@U=81@L=680433@","evaNr":"680433"},"richtung":"Bahnhof, Rothenburg ob der Tauber","echtzeitNotizen":[{"text":"Halt entfällt"}],"abgangsDatum":"2024-12-29T15:47:00+01:00","produktGattung":"BUS"}]}

View file

@ -1,27 +1,9 @@
import tap from 'tap'; import tap from 'tap';
import {parseLine as parse} from '../../parse/line.js'; import {parseLine as parse} from '../../parse/line.js';
import {products} from '../../lib/products.js';
const profile = { const profile = {
products: [ products: products,
{
id: 'nationalExpress',
mode: 'train',
name: 'InterCityExpress',
short: 'ICE',
vendo: 'ICE',
ris: 'HIGH_SPEED_TRAIN',
default: true,
},
{
id: 'bus',
mode: 'bus',
name: 'Bus',
short: 'B',
vendo: 'BUS',
ris: 'BUS',
default: true,
},
],
parseOperator: _ => null, parseOperator: _ => null,
}; };
const ctx = { const ctx = {
@ -172,3 +154,39 @@ tap.test('parses ris entry correctly', (t) => {
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
}); });
tap.test('parses dbnav ruf correctly', (t) => {
const input = {zuglaufId: 'foo', kurztext: 'RUF', mitteltext: 'RUF 9870', produktGattung: 'ANRUFPFLICHTIGEVERKEHRE'};
const expected = {
type: 'line',
id: 'ruf-9870',
name: 'RUF 9870',
fahrtNr: undefined,
public: true,
product: 'taxi',
productName: 'RUF',
mode: 'taxi',
operator: null,
};
t.same(parse(ctx, input), expected);
t.end();
});
tap.test('parses regio guide ruf correctly', (t) => {
const input = {train: {journeyId: 'foo', category: 'RUF', type: 'SHUTTLE', no: 47403, lineName: '9870'}, category: 'SHUTTLE', administration: {id: 'vrn062', operatorCode: 'DPN', operatorName: 'Nahreisezug'}};
const expected = {
type: 'line',
id: 'ruf-9870-47403',
name: 'RUF 9870',
fahrtNr: '47403',
public: true,
product: 'taxi',
productName: 'RUF',
mode: 'taxi',
operator: null,
};
t.same(parse(ctx, input), expected);
t.end();
});

View file

@ -37,3 +37,16 @@ tap.test('parses nothing', (t) => {
t.same(parse(ctx, op), null); t.same(parse(ctx, op), null);
t.end(); t.end();
}); });
tap.test('parses dbnav operator correctly', (t) => {
const op = [{text: 'DB Fernverkehr AG', key: 'OP'}];
t.same(parse(ctx, op), {
type: 'operator',
id: 'db-fernverkehr-ag',
name: 'DB Fernverkehr AG',
});
t.end();
});

View file

@ -190,3 +190,36 @@ tap.test('parses ris attributes correctly', (t) => {
t.same(parse(ctx, input), expected); t.same(parse(ctx, input), expected);
t.end(); t.end();
}); });
tap.test('parses dbnav attributes correctly', (t) => {
const input = {
echtzeitNotizen: [{text: 'Halt entfällt'}],
himNotizen: [{text: 'Coach 27 is closed to passengers today.', prio: 'NORMAL', ueberschrift: 'Information.', letzteAktualisierung: '2024-12-16T08:35:53+00:00'}],
attributNotizen: [{text: 'Komfort Check-in possible (visit bahn.de/kci for more information)', key: 'CK', priority: 200}, {text: 'DB Fernverkehr AG', key: 'OP'}],
};
const expected = [
{
code: undefined,
summary: 'Halt entfällt',
text: 'Halt entfällt',
type: 'warning',
},
{
code: undefined,
summary: 'Information.',
text: 'Coach 27 is closed to passengers today.',
modified: '2024-12-16T09:35:53+01:00',
type: 'status',
},
{
code: 'CK',
summary: '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',
},
];
t.same(parse(ctx, input), expected);
t.end();
});