diff --git a/lib/default-profile.js b/lib/default-profile.js index 0ee88225..4e7bf31a 100644 --- a/lib/default-profile.js +++ b/lib/default-profile.js @@ -20,7 +20,7 @@ import {parseLine} from '../parse/line.js'; import {parseLocation} from '../parse/location.js'; import {parsePolyline} from '../parse/polyline.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 {parseLoadFactor, parseArrOrDepWithLoadFactor} from '../parse/load-factor.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 {formatPoi} from '../format/poi.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 {formatLoyaltyCard} from '../format/loyalty-cards.js'; @@ -54,7 +54,7 @@ const defaultProfile = { ageGroup, ageGroupFromAge, ageGroupLabel, transformReqBody: id, transformReq: id, - randomizeUserAgent: true, + randomizeUserAgent: false, logRequest, logResponse, @@ -82,6 +82,7 @@ const defaultProfile = { parsePolyline, parseOperator, parseRemarks, + parseCancelled, parseStopover, parseLoadFactor, parseArrOrDepWithLoadFactor, @@ -99,6 +100,7 @@ const defaultProfile = { formatProductsFilter, formatStation, formatTime, + formatTimeOfDay, formatTravellers: notImplemented, formatRectangle: id, diff --git a/lib/products.js b/lib/products.js index d78eb83c..37648ba0 100644 --- a/lib/products.js +++ b/lib/products.js @@ -7,7 +7,9 @@ const products = [ short: 'ICE', vendo: 'ICE', ris: 'HIGH_SPEED_TRAIN', + ris_alt: 'HIGH_SPEED_TRAIN', dbnav: 'HOCHGESCHWINDIGKEITSZUEGE', + dbnav_short: 'ICE', default: true, }, { @@ -18,7 +20,9 @@ const products = [ short: 'IC/EC', vendo: 'EC_IC', ris: 'INTERCITY_TRAIN', + ris_alt: 'INTERCITY_TRAIN', dbnav: 'INTERCITYUNDEUROCITYZUEGE', + dbnav_short: 'IC_EC', default: true, }, { @@ -29,7 +33,9 @@ const products = [ short: 'RE/IR', vendo: 'IR', ris: 'INTER_REGIONAL_TRAIN', + ris_alt: 'INTER_REGIONAL_TRAIN', dbnav: 'INTERREGIOUNDSCHNELLZUEGE', + dbnav_short: 'IR', default: true, }, { @@ -40,7 +46,9 @@ const products = [ short: 'RB', vendo: 'REGIONAL', ris: 'REGIONAL_TRAIN', + ris_alt: 'REGIONAL_TRAIN', dbnav: 'NAHVERKEHRSONSTIGEZUEGE', + dbnav_short: 'RB', default: true, }, { @@ -51,7 +59,9 @@ const products = [ short: 'S', vendo: 'SBAHN', ris: 'CITY_TRAIN', + ris_alt: 'CITY_TRAIN', dbnav: 'SBAHNEN', + dbnav_short: 'SBAHN', default: true, }, { @@ -62,7 +72,9 @@ const products = [ short: 'B', vendo: 'BUS', ris: 'BUS', + ris_alt: 'BUS', dbnav: 'BUSSE', + dbnav_short: 'BUS', default: true, }, { @@ -73,7 +85,9 @@ const products = [ short: 'F', vendo: 'SCHIFF', ris: 'FERRY', + ris_alt: 'FERRY', dbnav: 'SCHIFFE', + dbnav_short: 'SCHIFF', default: true, }, { @@ -84,7 +98,9 @@ const products = [ short: 'U', vendo: 'UBAHN', ris: 'SUBWAY', + ris_alt: 'SUBWAY', dbnav: 'UBAHN', + dbnav_short: 'UBAHN', default: true, }, { @@ -95,7 +111,9 @@ const products = [ short: 'T', vendo: 'TRAM', ris: 'TRAM', + ris_alt: 'TRAM', dbnav: 'STRASSENBAHN', + dbnav_short: 'STR', default: true, }, { @@ -106,7 +124,9 @@ const products = [ short: 'Taxi', vendo: 'ANRUFPFLICHTIG', ris: 'TAXI', + ris_alt: 'SHUTTLE', dbnav: 'ANRUFPFLICHTIGEVERKEHRE', + dbnav_short: 'ANRUFPFLICHTIGEVERKEHRE', default: true, }, ]; diff --git a/p/dbnav/base.json b/p/dbnav/base.json index bd4f9cc5..344e7b4a 100644 --- a/p/dbnav/base.json +++ b/p/dbnav/base.json @@ -5,6 +5,6 @@ "locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search", "nearbyEndpoint": "https://app.vendo.noncd.db.de/mob/location/nearby", "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" } diff --git a/p/dbnav/station-board-req.js b/p/dbnav/station-board-req.js index f99744c0..b1faa85e 100644 --- a/p/dbnav/station-board-req.js +++ b/p/dbnav/station-board-req.js @@ -8,7 +8,7 @@ const formatStationBoardReq = (ctx, station, type) => { 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')}, method: 'POST', - header: getHeaders('application/x.db.vendo.mob.bahnhofstafeln.v2+json'), + headers: getHeaders('application/x.db.vendo.mob.bahnhofstafeln.v2+json'), }; }; diff --git a/parse/arrival-or-departure.js b/parse/arrival-or-departure.js index 8f972092..9c45ef8c 100644 --- a/parse/arrival-or-departure.js +++ b/parse/arrival-or-departure.js @@ -8,15 +8,20 @@ const createParseArrOrDep = (prefix) => { const parseArrOrDep = (ctx, d) => { // d = raw arrival/departure const {profile, opt} = ctx; - + const cancelled = profile.parseCancelled(d); const res = { - tripId: d.journeyID || d.train.journeyId, - stop: profile.parseLocation(ctx, d.station), - ...profile.parseWhen(ctx, null, d.timeSchedule ? d.timeSchedule : d.time, d.timeType != 'SCHEDULE' ? d.timePredicted ? d.timePredicted : d.time : null, d.canceled), - ...profile.parsePlatform(ctx, d.platformSchedule ? d.platformSchedule : d.platform, d.platformPredicted ? d.platformPredicted : d.platform, d.canceled), + tripId: d.journeyID || d.train?.journeyId || d.zuglaufId, + stop: profile.parseLocation(ctx, d.station || d.abfrageOrt), + ...profile.parseWhen( + 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 - direction: d.transport?.direction?.stopPlaces?.length > 0 && profile.parseStationName(ctx, d.transport?.direction?.stopPlaces[0].name) || profile.parseStationName(ctx, d.destination?.name) || null, - provenance: profile.parseStationName(ctx, d.transport?.origin?.name) || profile.parseStationName(ctx, d.origin?.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 || d.origin?.name || d.abgangsOrt?.name) || null, line: profile.parseLine(ctx, d) || null, remarks: [], origin: profile.parseLocation(ctx, d.transport?.origin || d.origin) || null, @@ -26,7 +31,7 @@ const createParseArrOrDep = (prefix) => { // TODO pos - if (d.canceled) { + if (cancelled) { res.cancelled = true; Object.defineProperty(res, 'canceled', {value: true}); } diff --git a/parse/journey-leg.js b/parse/journey-leg.js index cfb5e1e2..5dc31867 100644 --- a/parse/journey-leg.js +++ b/parse/journey-leg.js @@ -1,5 +1,3 @@ -import {parseRemarks, isStopCancelled} from './remarks.js'; - const locationFallback = (id, name, fallbackLocations) => { if (fallbackLocations && (id && fallbackLocations[id] || name && 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), }; - 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); res.departure = dep.when; res.plannedDeparture = dep.plannedWhen; @@ -29,7 +27,7 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg 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); res.arrival = arr.when; 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); } if (opt.remarks) { - res.remarks = parseRemarks(ctx, pt); + res.remarks = profile.parseRemarks(ctx, pt); } } diff --git a/parse/line.js b/parse/line.js index 3de8d7be..8941c423 100644 --- a/parse/line.js +++ b/parse/line.js @@ -5,19 +5,19 @@ const parseLine = (ctx, p) => { const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no; const res = { type: 'line', - id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription || p.train?.no), // TODO terrible - fahrtNr: fahrtNr ? String(fahrtNr) : undefined, - name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription || p.train && p.train.category + ' ' + p.train.lineName, + 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, // TODO extract from zuglaufId? + name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription || p.train && p.train.category + ' ' + p.train.lineName || p.langtext || p.mitteltext, public: true, }; // TODO res.adminCode - res.productName = p.verkehrsmittel?.kurzText || p.transport?.category || p.train?.category; - const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type); + 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 || pp.ris_alt == p.train?.type || pp.dbnav_short == p.produktGattung); res.mode = foundProduct?.mode; 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; }; diff --git a/parse/location.js b/parse/location.js index 8c2073c3..a8e8dd65 100644 --- a/parse/location.js +++ b/parse/location.js @@ -28,15 +28,15 @@ const parseLocation = (ctx, l) => { 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 = { type: 'station', id: res.id, 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 if ('products' in l) { @@ -57,10 +57,10 @@ const parseLocation = (ctx, l) => { } res.name = name; - if (l.type === ADDRESS || lid.A == 2) { + if (l.type === ADDRESS || lid.A == '2') { res.address = name; } - if (l.type === POI || lid.A == 4) { + if (l.type === POI || lid.A == '4') { res.poi = true; } diff --git a/parse/operator.js b/parse/operator.js index 38c52046..8c39ab00 100644 --- a/parse/operator.js +++ b/parse/operator.js @@ -4,17 +4,17 @@ const parseOperator = (ctx, zugattrib) => { if (!zugattrib) { return null; } - const bef = zugattrib.find(z => z.key == 'BEF'); + const bef = zugattrib.find(z => z.key == 'BEF' || z.key == 'OP'); if (!bef) { return null; } - const name = bef.value && bef.value.trim(); + const name = bef.value || bef.text; if (!name) { return null; } return { type: 'operator', - id: slugg(name), // todo: find a more reliable way + id: slugg(name.trim()), // todo: find a more reliable way name, }; }; diff --git a/parse/remarks.js b/parse/remarks.js index 8ca4d620..022dedc9 100644 --- a/parse/remarks.js +++ b/parse/remarks.js @@ -3,44 +3,53 @@ import flatMap from 'lodash/flatMap.js'; const parseRemarks = (ctx, ref) => { // TODO ereignisZusammenfassung, priorisierteMeldungen? return flatMap([ - ref.risNotizen || [], - ref.himMeldungen || [], - ref.meldungenAsObject || [], - ref.verkehrsmittel?.zugattribute || [], - ref.messages || [], - ref.attributes || [], 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 => { - if (remark.kategorie) { + if (remark.kategorie || remark.priority) { const res = ctx.profile.parseHintByCode(remark); if (res) { return res; } } let type = 'hint'; - if (remark.prioritaet || remark.type) { + if (remark.prioritaet || remark.prio || remark.type) { 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'; } let res = { 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, - text: remark.nachrichtLang || remark.value || remark.text || Object.values(remark.descriptions || {}) + text: remark.nachrichtLang || remark.value || remark.text + || Object.values(remark.descriptions || {}) .shift()?.text, type: type, }; - if (remark.modDateTime) { - res.modified = ctx.profile.parseDateTime(ctx, null, remark.modDateTime); + if (remark.modDateTime || remark.letzteAktualisierung) { + res.modified = ctx.profile.parseDateTime(ctx, null, remark.modDateTime || remark.letzteAktualisierung); } // TODO fromStops, toStops = routeIdxFrom ?? // TODO prio 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) => { - return Boolean(ref.risNotizen.find(r => r.key == 'text.realtime.stop.cancelled' || r.type == 'HALT_AUSFALL')); +const parseCancelled = (ref) => { + 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 { parseRemarks, - isStopCancelled, + parseCancelled, }; diff --git a/parse/stopover.js b/parse/stopover.js index 7f80e2e4..247ab8cc 100644 --- a/parse/stopover.js +++ b/parse/stopover.js @@ -1,9 +1,7 @@ -import {parseRemarks, isStopCancelled} from './remarks.js'; - const parseStopover = (ctx, st, date) => { // st = raw stopover 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 arrPl = profile.parsePlatform(ctx, st.gleis, st.ezGleis); 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; if (opt.remarks) { - res.remarks = parseRemarks(ctx, st); + res.remarks = profile.parseRemarks(ctx, st); } return res; diff --git a/test/dbnav-departures.js b/test/dbnav-departures.js new file mode 100644 index 00000000..a04893a0 --- /dev/null +++ b/test/dbnav-departures.js @@ -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(); +}); diff --git a/test/fixtures/db-arrivals.js b/test/fixtures/db-arrivals.js index 11d3b08e..e796a266 100644 --- a/test/fixtures/db-arrivals.js +++ b/test/fixtures/db-arrivals.js @@ -5,7 +5,6 @@ const dbArrivals = [ type: 'station', id: '8089100', name: 'Berlin Jungfernheide (S)', - location: null, }, when: '2024-12-08T01:00:00+01:00', plannedWhen: '2024-12-08T01:00:00+01:00', @@ -37,7 +36,6 @@ const dbArrivals = [ type: 'station', id: '8089118', name: 'Berlin Beusselstraße', - location: null, }, destination: null, }, @@ -47,7 +45,6 @@ const dbArrivals = [ type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', - location: null, }, when: '2024-12-08T01:00:00+01:00', plannedWhen: '2024-12-08T01:00:00+01:00', @@ -85,7 +82,6 @@ const dbArrivals = [ type: 'station', id: '732218', name: 'Rudow (U), Berlin', - location: null, }, destination: null, }, @@ -95,7 +91,6 @@ const dbArrivals = [ type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', - location: null, }, when: '2024-12-08T01:03:00+01:00', plannedWhen: '2024-12-08T01:03:00+01:00', @@ -133,7 +128,6 @@ const dbArrivals = [ type: 'station', id: '730993', name: 'Goerdelersteg, Berlin', - location: null, }, destination: null, }, @@ -143,7 +137,6 @@ const dbArrivals = [ type: 'station', id: '8089100', name: 'Berlin Jungfernheide (S)', - location: null, }, when: '2024-12-08T01:05:00+01:00', plannedWhen: '2024-12-08T01:05:00+01:00', @@ -175,7 +168,6 @@ const dbArrivals = [ type: 'station', id: '8089118', name: 'Berlin Beusselstraße', - location: null, }, destination: null, }, @@ -185,7 +177,6 @@ const dbArrivals = [ type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', - location: null, }, when: '2024-12-08T01:10:00+01:00', plannedWhen: '2024-12-08T01:10:00+01:00', @@ -223,7 +214,6 @@ const dbArrivals = [ type: 'station', id: '732218', name: 'Rudow (U), Berlin', - location: null, }, destination: null, }, @@ -233,7 +223,6 @@ const dbArrivals = [ type: 'station', id: '8089100', name: 'Berlin Jungfernheide (S)', - location: null, }, when: '2024-12-08T01:10:00+01:00', plannedWhen: '2024-12-08T01:10:00+01:00', @@ -265,7 +254,6 @@ const dbArrivals = [ type: 'station', id: '8089118', name: 'Berlin Beusselstraße', - location: null, }, destination: null, }, @@ -275,7 +263,6 @@ const dbArrivals = [ type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', - location: null, }, when: '2024-12-08T01:10:00+01:00', plannedWhen: '2024-12-08T01:10:00+01:00', @@ -313,7 +300,6 @@ const dbArrivals = [ type: 'station', id: '731176', name: 'Rathaus Spandau (S+U), Berlin', - location: null, }, destination: null, }, diff --git a/test/fixtures/db-departures-regio-guide.js b/test/fixtures/db-departures-regio-guide.js index 8bd83e0f..81a4b79b 100644 --- a/test/fixtures/db-departures-regio-guide.js +++ b/test/fixtures/db-departures-regio-guide.js @@ -5,7 +5,6 @@ const dbDepartures = [ type: 'station', id: '8000365', name: 'Dombühl', - location: null, }, when: '2024-12-12T12:34:00+01:00', plannedWhen: '2024-12-12T12:34:00+01:00', @@ -16,7 +15,7 @@ const dbDepartures = [ provenance: null, line: { type: 'line', - id: '88617', + id: 're-90-88617', fahrtNr: '88617', name: 'RE 90', public: true, @@ -31,7 +30,6 @@ const dbDepartures = [ type: 'station', id: '8000284', name: 'Nürnberg Hbf', - location: null, }, }, { @@ -40,7 +38,6 @@ const dbDepartures = [ type: 'station', id: '682943', name: 'Bahnhof, Dombühl', - location: null, }, when: '2024-12-12T12:50:00+01:00', plannedWhen: '2024-12-12T12:50:00+01:00', @@ -51,7 +48,7 @@ const dbDepartures = [ provenance: null, line: { type: 'line', - id: '2221', + id: 'bus-813-2221', fahrtNr: '2221', name: 'Bus 813', public: true, @@ -66,7 +63,6 @@ const dbDepartures = [ type: 'station', id: '676542', name: 'Gymnasium, Dinkelsbühl', - location: null, }, }, { @@ -75,7 +71,6 @@ const dbDepartures = [ type: 'station', id: '682943', name: 'Bahnhof, Dombühl', - location: null, }, when: '2024-12-12T12:50:00+01:00', plannedWhen: '2024-12-12T12:50:00+01:00', @@ -86,7 +81,7 @@ const dbDepartures = [ provenance: null, line: { type: 'line', - id: '2177', + id: 'bus-807-2177', fahrtNr: '2177', name: 'Bus 807', public: true, @@ -101,7 +96,6 @@ const dbDepartures = [ type: 'station', id: '683407', name: 'Bahnhof, Rothenburg ob der Tauber', - location: null, }, }, ]; diff --git a/test/fixtures/dbnav-departures.js b/test/fixtures/dbnav-departures.js new file mode 100644 index 00000000..b2b625e6 --- /dev/null +++ b/test/fixtures/dbnav-departures.js @@ -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, +}; diff --git a/test/fixtures/dbnav-departures.json b/test/fixtures/dbnav-departures.json new file mode 100644 index 00000000..ddc67cac --- /dev/null +++ b/test/fixtures/dbnav-departures.json @@ -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"}]} \ No newline at end of file diff --git a/test/parse/line.js b/test/parse/line.js index 4f060f6b..3838243d 100644 --- a/test/parse/line.js +++ b/test/parse/line.js @@ -1,27 +1,9 @@ import tap from 'tap'; import {parseLine as parse} from '../../parse/line.js'; +import {products} from '../../lib/products.js'; const profile = { - 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, - }, - ], + products: products, parseOperator: _ => null, }; const ctx = { @@ -172,3 +154,39 @@ tap.test('parses ris entry correctly', (t) => { t.same(parse(ctx, input), expected); 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(); +}); diff --git a/test/parse/operator.js b/test/parse/operator.js index cbd5044d..fc47e219 100644 --- a/test/parse/operator.js +++ b/test/parse/operator.js @@ -37,3 +37,16 @@ tap.test('parses nothing', (t) => { t.same(parse(ctx, op), null); 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(); +}); + diff --git a/test/parse/remarks.js b/test/parse/remarks.js index 22636fe7..5002a95d 100644 --- a/test/parse/remarks.js +++ b/test/parse/remarks.js @@ -190,3 +190,36 @@ tap.test('parses ris attributes correctly', (t) => { t.same(parse(ctx, input), expected); 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(); +}); +