mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-05-29 08:35:02 +03:00
Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b59d7b3084 | ||
|
db4c03054a | ||
|
eac21d188b | ||
|
ad09f8b1be | ||
|
c4d0a55d41 | ||
|
29aab87cdf | ||
|
883eb8c8de | ||
|
b20cf1060a | ||
|
b887c674d4 | ||
|
b3e0e764e2 | ||
|
2ea47f7792 | ||
|
6c2081c14e | ||
|
f741a13670 | ||
|
bcaad526c7 | ||
|
162b946bac | ||
|
14b80dbf33 | ||
|
1927f98906 | ||
|
0ef3935a35 | ||
|
b04a671b50 | ||
|
9975a6c9ac |
55 changed files with 5082 additions and 2151 deletions
6
api.js
6
api.js
|
@ -2,6 +2,9 @@ import {createClient} from './index.js';
|
||||||
import {profile as dbProfile} from './p/db/index.js';
|
import {profile as dbProfile} from './p/db/index.js';
|
||||||
import {profile as dbnavProfile} from './p/dbnav/index.js';
|
import {profile as dbnavProfile} from './p/dbnav/index.js';
|
||||||
import {profile as dbwebProfile} from './p/dbweb/index.js';
|
import {profile as dbwebProfile} from './p/dbweb/index.js';
|
||||||
|
import {profile as dbrisProfile} from './p/dbris/index.js';
|
||||||
|
import {profile as dbbahnhofProfile} from './p/dbbahnhof/index.js';
|
||||||
|
import {profile as dbregioguideProfile} from './p/dbregioguide/index.js';
|
||||||
import {mapRouteParsers} from './lib/api-parsers.js';
|
import {mapRouteParsers} from './lib/api-parsers.js';
|
||||||
import {createHafasRestApi as createApi} from 'hafas-rest-api';
|
import {createHafasRestApi as createApi} from 'hafas-rest-api';
|
||||||
|
|
||||||
|
@ -26,6 +29,9 @@ const profiles = {
|
||||||
db: dbProfile,
|
db: dbProfile,
|
||||||
dbnav: dbnavProfile,
|
dbnav: dbnavProfile,
|
||||||
dbweb: dbwebProfile,
|
dbweb: dbwebProfile,
|
||||||
|
dbris: dbrisProfile,
|
||||||
|
dbbahnhof: dbbahnhofProfile,
|
||||||
|
dbregioguide: dbregioguideProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
|
|
|
@ -621,13 +621,22 @@
|
||||||
"Zusammenfassung",
|
"Zusammenfassung",
|
||||||
"Züttlingen",
|
"Züttlingen",
|
||||||
"Zwickauer",
|
"Zwickauer",
|
||||||
"zwischenhalte"
|
"zwischenhalte",
|
||||||
|
"bestprice",
|
||||||
|
"intervalle",
|
||||||
|
"Intervalle",
|
||||||
|
"tagesbest",
|
||||||
|
"dbbahnhof",
|
||||||
|
"Deutschlandticket",
|
||||||
|
"fahrverguenstigungen",
|
||||||
|
"cancelation"
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"docs/dumps/**",
|
"docs/dumps/**",
|
||||||
"test/e2e/fixtures/**",
|
"test/e2e/fixtures/**",
|
||||||
|
"test/fixtures/**",
|
||||||
"test/parse/remarks.js",
|
"test/parse/remarks.js",
|
||||||
"test/parse/dbnav-journey.js"
|
"test/parse/dbnav-journey.js"
|
||||||
],
|
],
|
||||||
"dictionaries": [
|
"dictionaries": [
|
||||||
"node"
|
"node"
|
||||||
|
|
|
@ -36,11 +36,12 @@ With `opt`, you can override the default options, which look like this:
|
||||||
// departures at related stations
|
// departures at related stations
|
||||||
// e.g. those that belong together on the metro map.
|
// e.g. those that belong together on the metro map.
|
||||||
includeRelatedStations: true,
|
includeRelatedStations: true,
|
||||||
|
moreStops: null // also include departures/arrivals for array of up to nine additional station evaNumbers (not supported with dbnav and dbweb)
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The maximum supported duration is 720 for `db` and 60 for other profiles.
|
The maximum supported duration depends on the profile (see main readme), e.g. 720 for `db` and 60 for `dbnav`. In order to use the `dbris` profile, you need to pass the env vars `DB_API_KEY` and `DB_CLIENT_ID`.
|
||||||
If you pass an object `opt.products`, its fields will partially override the default products defined in the profile.
|
If you pass an object `opt.products`, its fields will partially override the default products defined in the profile.
|
||||||
|
|
||||||
## Response
|
## Response
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,9 @@ With `opt`, you can override the default options, which look like this:
|
||||||
subStops: true, // not supported
|
subStops: true, // not supported
|
||||||
entrances: true, // not supported
|
entrances: true, // not supported
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
scheduledDays: false, // not yet supported
|
scheduledDays: false, // returns a field `serviceDays` (instead of `scheduledDays` in hafas-client!) with a different, human-readable structure
|
||||||
|
notOnlyFastRoutes: false, // if true, also show journeys that are mathematically non-optimal
|
||||||
|
bestprice: false, // search for lowest prices across the entire day, returns list of journeys sorted by price
|
||||||
firstClass: false, // first or second class for tickets
|
firstClass: false, // first or second class for tickets
|
||||||
loyaltyCard: null, // BahnCards etc., see below
|
loyaltyCard: null, // BahnCards etc., see below
|
||||||
language: 'en', // language to get results in
|
language: 'en', // language to get results in
|
||||||
|
|
|
@ -10,7 +10,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
{
|
{
|
||||||
results: 8, // maximum number of results
|
results: 8, // maximum number of results
|
||||||
distance: null, // maximum walking distance in meters
|
distance: null, // maximum walking distance in meters
|
||||||
poi: false, // return points of interest?
|
poi: false, // not supported
|
||||||
stops: true, // return stops/stations?
|
stops: true, // return stops/stations?
|
||||||
subStops: true, // not supported
|
subStops: true, // not supported
|
||||||
entrances: true, // not supported
|
entrances: true, // not supported
|
||||||
|
|
|
@ -365,7 +365,19 @@ paths:
|
||||||
default: true
|
default: true
|
||||||
- name: scheduledDays
|
- name: scheduledDays
|
||||||
in: query
|
in: query
|
||||||
description: Parse & return dates each journey is valid on?
|
description: Parse & return dates the journey is valid on?, returns a field `serviceDays` (instead of `scheduledDays` in hafas-client!) with a different, human-readable structure
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: notOnlyFastRoutes
|
||||||
|
in: query
|
||||||
|
description: if true, also show journeys that are mathematically non-optimal
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: bestprice
|
||||||
|
in: query
|
||||||
|
description: search for lowest prices across the entire day
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
@ -601,7 +613,7 @@ paths:
|
||||||
default: true
|
default: true
|
||||||
- name: linesOfStops
|
- name: linesOfStops
|
||||||
in: query
|
in: query
|
||||||
description: Parse & return lines of each stop/station?
|
description: not supported
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
@ -683,7 +695,19 @@ paths:
|
||||||
default: true
|
default: true
|
||||||
- name: scheduledDays
|
- name: scheduledDays
|
||||||
in: query
|
in: query
|
||||||
description: Parse & return dates the journey is valid on?
|
description: Parse & return dates the journey is valid on?, returns a field `serviceDays` (instead of `scheduledDays` in hafas-client!) with a different, human-readable structure
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: notOnlyFastRoutes
|
||||||
|
in: query
|
||||||
|
description: if true, also show journeys that are mathematically non-optimal
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: bestprice
|
||||||
|
in: query
|
||||||
|
description: search for lowest prices across the entire day
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
@ -1863,11 +1887,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
@ -1971,11 +1995,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
@ -2010,15 +2034,15 @@ components:
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
linesOfStops:
|
linesOfStops:
|
||||||
description: parse & expose lines at each stop/station?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
language:
|
language:
|
||||||
|
@ -2037,11 +2061,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
@ -2064,11 +2088,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
@ -2104,11 +2128,11 @@ components:
|
||||||
default: 10
|
default: 10
|
||||||
type: number
|
type: number
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
linesOfStops:
|
linesOfStops:
|
||||||
|
@ -2182,7 +2206,7 @@ components:
|
||||||
default: undefined
|
default: undefined
|
||||||
type: number
|
type: number
|
||||||
poi:
|
poi:
|
||||||
description: return points of interest?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
stops:
|
stops:
|
||||||
|
@ -2194,15 +2218,15 @@ components:
|
||||||
description: products
|
description: products
|
||||||
default: undefined
|
default: undefined
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
linesOfStops:
|
linesOfStops:
|
||||||
description: parse & expose lines at each stop/station?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
language:
|
language:
|
||||||
|
@ -2230,11 +2254,11 @@ components:
|
||||||
description: products
|
description: products
|
||||||
default: undefined
|
default: undefined
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
polylines:
|
polylines:
|
||||||
|
@ -2272,11 +2296,11 @@ components:
|
||||||
default: 20
|
default: 20
|
||||||
type: number
|
type: number
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
polylines:
|
polylines:
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
const formatStationBoardReq = (ctx, station, type) => {
|
|
||||||
const {profile, opt} = ctx;
|
|
||||||
|
|
||||||
return {
|
|
||||||
endpoint: profile.boardEndpoint,
|
|
||||||
path: (type == 'departures' ? 'departure' : 'arrival') + '/' + station,
|
|
||||||
query: {
|
|
||||||
// TODO direction, fields below
|
|
||||||
modeOfTransport: profile.formatProductsFilter(ctx, opt.products || {}, 'ris'),
|
|
||||||
timeStart: profile.formatTime(profile, opt.when, true),
|
|
||||||
timeEnd: profile.formatTime(profile, opt.when.getTime() + opt.duration * 60 * 1000, true),
|
|
||||||
expandTimeFrame: 'TIME_END', // TODO impact?
|
|
||||||
},
|
|
||||||
method: 'get',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO separate RIS::Boards profile?
|
|
||||||
const formatRisStationBoardReq = (ctx, station, type) => {
|
|
||||||
const {profile, opt} = ctx;
|
|
||||||
|
|
||||||
return {
|
|
||||||
endpoint: profile.boardEndpoint,
|
|
||||||
path: type + '/' + station,
|
|
||||||
query: {
|
|
||||||
// TODO direction
|
|
||||||
filterTransports: profile.formatProductsFilter(ctx, opt.products || {}, 'ris'),
|
|
||||||
timeStart: profile.formatTime(profile, opt.when, true),
|
|
||||||
timeEnd: profile.formatTime(profile, opt.when.getTime() + opt.duration * 60 * 1000, true),
|
|
||||||
maxViaStops: opt.stopovers ? undefined : 0,
|
|
||||||
includeStationGroup: opt.includeRelatedStations,
|
|
||||||
maxTransportsPerType: opt.results === Infinity ? undefined : opt.results,
|
|
||||||
includeMessagesDisruptions: opt.remarks,
|
|
||||||
sortBy: 'TIME_SCHEDULE',
|
|
||||||
},
|
|
||||||
method: 'get',
|
|
||||||
headers: {
|
|
||||||
'Db-Client-Id': process.env.DB_CLIENT_ID,
|
|
||||||
'Db-Api-Key': process.env.DB_API_KEY,
|
|
||||||
'Accept': 'application/vnd.de.db.ris+json',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
export {
|
|
||||||
formatStationBoardReq,
|
|
||||||
};
|
|
52
index.js
52
index.js
|
@ -27,25 +27,18 @@ const validateLocation = (loc, name = 'location') => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadEnrichedStationData = (profile) => new Promise((resolve, reject) => {
|
const loadEnrichedStationData = async (profile) => {
|
||||||
import('db-hafas-stations').then(m => {
|
const dbHafasStations = await import('db-hafas-stations');
|
||||||
const items = {};
|
const items = {};
|
||||||
m.default.full()
|
for await (const station of dbHafasStations.readFullStations()) {
|
||||||
.on('data', (station) => {
|
items[station.id] = station;
|
||||||
items[station.id] = station;
|
items[station.name] = station;
|
||||||
items[station.name] = station;
|
}
|
||||||
})
|
if (profile.DEBUG) {
|
||||||
.once('end', () => {
|
console.log('Loaded station index.');
|
||||||
if (profile.DEBUG) {
|
}
|
||||||
console.log('Loaded station index.');
|
return items;
|
||||||
}
|
};
|
||||||
resolve(items);
|
|
||||||
})
|
|
||||||
.once('error', (err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const applyEnrichedStationData = async (ctx, shouldLoadEnrichedStationData) => {
|
const applyEnrichedStationData = async (ctx, shouldLoadEnrichedStationData) => {
|
||||||
const {profile, common} = ctx;
|
const {profile, common} = ctx;
|
||||||
|
@ -85,10 +78,10 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
throw new TypeError('type must be a non-empty string.');
|
throw new TypeError('type must be a non-empty string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!profile.departuresGetPasslist && 'stopovers' in opt) {
|
if (!profile.departuresGetPasslist && opt.stopovers) {
|
||||||
throw new Error('opt.stopovers is not supported by this endpoint');
|
throw new Error('opt.stopovers is not supported by this endpoint');
|
||||||
}
|
}
|
||||||
if (!profile.departuresStbFltrEquiv && 'includeRelatedStations' in opt) { // TODO artificially filter?
|
if (!profile.departuresStbFltrEquiv && 'includeRelatedStations' in opt) {
|
||||||
throw new Error('opt.includeRelatedStations is not supported by this endpoint');
|
throw new Error('opt.includeRelatedStations is not supported by this endpoint');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +99,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
// departures at related stations
|
// departures at related stations
|
||||||
// e.g. those that belong together on the metro map.
|
// e.g. those that belong together on the metro map.
|
||||||
includeRelatedStations: true,
|
includeRelatedStations: true,
|
||||||
|
moreStops: null, // also include departures/arrivals for array of up to nine additional station evaNumbers (not supported with dbnav and dbweb)
|
||||||
}, opt);
|
}, opt);
|
||||||
opt.when = new Date(opt.when || Date.now());
|
opt.when = new Date(opt.when || Date.now());
|
||||||
if (Number.isNaN(Number(opt.when))) {
|
if (Number.isNaN(Number(opt.when))) {
|
||||||
|
@ -117,7 +111,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
const {res} = await profile.request({profile, opt}, userAgent, req);
|
const {res} = await profile.request({profile, opt}, userAgent, req);
|
||||||
|
|
||||||
const ctx = {profile, opt, common, res};
|
const ctx = {profile, opt, common, res};
|
||||||
let results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries)
|
let results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries.flat())
|
||||||
.map(res => parse(ctx, res)); // TODO sort?, slice
|
.map(res => parse(ctx, res)); // TODO sort?, slice
|
||||||
|
|
||||||
if (!opt.includeRelatedStations) {
|
if (!opt.includeRelatedStations) {
|
||||||
|
@ -185,6 +179,10 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // parse & expose entrances of stops/stations?
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
scheduledDays: false, // parse & expose dates each journey is valid on?
|
scheduledDays: false, // parse & expose dates each journey is valid on?
|
||||||
|
notOnlyFastRoutes: false, // if true, also show routes that are mathematically non-optimal
|
||||||
|
bestprice: false, // search for lowest prices across the entire day
|
||||||
|
deutschlandTicketDiscount: false,
|
||||||
|
deutschlandTicketConnectionsOnly: false,
|
||||||
}, opt);
|
}, opt);
|
||||||
|
|
||||||
if (opt.when !== undefined) {
|
if (opt.when !== undefined) {
|
||||||
|
@ -210,9 +208,15 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
const req = profile.formatJourneysReq({profile, opt}, from, to, when, outFrwd, journeysRef);
|
const req = profile.formatJourneysReq({profile, opt}, from, to, when, outFrwd, journeysRef);
|
||||||
const {res} = await profile.request({profile, opt}, userAgent, req);
|
const {res} = await profile.request({profile, opt}, userAgent, req);
|
||||||
const ctx = {profile, opt, common, res};
|
const ctx = {profile, opt, common, res};
|
||||||
const verbindungen = Number.isInteger(opt.results) ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
|
if (opt.bestprice) {
|
||||||
|
res.verbindungen = (res.intervalle || res.tagesbestPreisIntervalle).flatMap(i => i.verbindungen.map(v => ({...v, ...v.verbindung})));
|
||||||
|
}
|
||||||
|
const verbindungen = Number.isInteger(opt.results) && opt.results != 3 ? res.verbindungen.slice(0, opt.results) : res.verbindungen; // TODO remove default from hafas-rest-api
|
||||||
const journeys = verbindungen
|
const journeys = verbindungen
|
||||||
.map(j => profile.parseJourney(ctx, j));
|
.map(j => profile.parseJourney(ctx, j));
|
||||||
|
if (opt.bestprice) {
|
||||||
|
journeys.sort((a, b) => a.price?.amount - b.price?.amount);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
earlierRef: res.verbindungReference?.earlier || res.frueherContext || null,
|
earlierRef: res.verbindungReference?.earlier || res.frueherContext || null,
|
||||||
|
@ -237,6 +241,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // parse & expose entrances of stops/stations?
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
scheduledDays: false, // parse & expose dates the journey is valid on?
|
scheduledDays: false, // parse & expose dates the journey is valid on?
|
||||||
|
deutschlandTicketDiscount: false,
|
||||||
|
deutschlandTicketConnectionsOnly: false,
|
||||||
}, opt);
|
}, opt);
|
||||||
|
|
||||||
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken);
|
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {data as cards} from '../format/loyalty-cards.js';
|
import {data as cards} from '../format/loyalty-cards.js';
|
||||||
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js';
|
import {parseBoolean, parseInteger, parseArrayOfStrings} from 'hafas-rest-api/lib/parse.js';
|
||||||
|
|
||||||
const typesByName = new Map([
|
const typesByName = new Map([
|
||||||
['bahncard-1st-25', {type: cards.BAHNCARD, discount: 25, class: 1}],
|
['bahncard-1st-25', {type: cards.BAHNCARD, discount: 25, class: 1}],
|
||||||
|
@ -42,31 +42,66 @@ const parseArrayOr = (parseEntry) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapRouteParsers = (route, parsers) => {
|
const mapRouteParsers = (route, parsers) => {
|
||||||
if (route !== 'journeys') {
|
if (route.includes('journey')) {
|
||||||
return parsers;
|
return {
|
||||||
|
...parsers,
|
||||||
|
firstClass: {
|
||||||
|
description: 'Search for first-class options?',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
loyaltyCard: {
|
||||||
|
description: 'Type of loyalty card in use.',
|
||||||
|
type: 'string',
|
||||||
|
enum: types,
|
||||||
|
defaultStr: '*none*',
|
||||||
|
parse: parseArrayOr(parseLoyaltyCard),
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
description: 'Age of traveller',
|
||||||
|
type: 'integer',
|
||||||
|
defaultStr: '*adult*',
|
||||||
|
parse: parseArrayOr(parseInteger),
|
||||||
|
},
|
||||||
|
notOnlyFastRoutes: {
|
||||||
|
description: 'If true, also show routes that are mathematically non-optimal',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
bestprice: {
|
||||||
|
description: 'Search for lowest prices across the entire day',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
deutschlandTicketDiscount: {
|
||||||
|
description: 'Calculate ticket prices assuming Deutschlandticket is present',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
deutschlandTicketConnectionsOnly: {
|
||||||
|
description: 'Only return journeys that can be used with the Deutschlandticket',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
if (route.includes('departures') || route.includes('arrivals')) {
|
||||||
...parsers,
|
return {
|
||||||
firstClass: {
|
...parsers,
|
||||||
description: 'Search for first-class options?',
|
moreStops: {
|
||||||
type: 'boolean',
|
description: 'Also include departures/arrivals for up to nine comma-separated station evaNumbers (not supported with dbnav and dbweb)',
|
||||||
default: 'false',
|
type: 'string',
|
||||||
parse: parseBoolean,
|
default: '',
|
||||||
},
|
parse: parseArrayOfStrings,
|
||||||
loyaltyCard: {
|
},
|
||||||
description: 'Type of loyalty card in use.',
|
};
|
||||||
type: 'string',
|
}
|
||||||
enum: types,
|
return parsers;
|
||||||
defaultStr: '*none*',
|
|
||||||
parse: parseArrayOr(parseLoyaltyCard),
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
description: 'Age of traveller',
|
|
||||||
type: 'integer',
|
|
||||||
defaultStr: '*adult*',
|
|
||||||
parse: parseArrayOr(parseInteger),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -2,10 +2,6 @@ import {request} from '../lib/request.js';
|
||||||
import {products} from '../lib/products.js';
|
import {products} from '../lib/products.js';
|
||||||
import {ageGroup, ageGroupFromAge, ageGroupLabel} from './age-group.js';
|
import {ageGroup, ageGroupFromAge, ageGroupLabel} from './age-group.js';
|
||||||
|
|
||||||
import {formatStationBoardReq} from '../format/station-board-req.js';
|
|
||||||
import {formatTripReq} from '../format/trip-req.js';
|
|
||||||
import {formatNearbyReq} from '../format/nearby-req.js';
|
|
||||||
|
|
||||||
import {parseDateTime} from '../parse/date-time.js';
|
import {parseDateTime} from '../parse/date-time.js';
|
||||||
import {parsePlatform} from '../parse/platform.js';
|
import {parsePlatform} from '../parse/platform.js';
|
||||||
import {parseProducts} from '../parse/products.js';
|
import {parseProducts} from '../parse/products.js';
|
||||||
|
@ -61,13 +57,13 @@ const defaultProfile = {
|
||||||
logRequest,
|
logRequest,
|
||||||
logResponse,
|
logResponse,
|
||||||
|
|
||||||
formatStationBoardReq,
|
|
||||||
formatLocationsReq: notImplemented,
|
|
||||||
formatStopReq: notImplemented,
|
|
||||||
formatTripReq,
|
|
||||||
formatNearbyReq,
|
|
||||||
formatJourneysReq: notImplemented,
|
formatJourneysReq: notImplemented,
|
||||||
formatRefreshJourneyReq: notImplemented,
|
formatRefreshJourneyReq: notImplemented,
|
||||||
|
formatTripReq: notImplemented,
|
||||||
|
formatNearbyReq: notImplemented,
|
||||||
|
formatLocationsReq: notImplemented,
|
||||||
|
formatStopReq: notImplemented,
|
||||||
|
formatStationBoardReq: notImplemented,
|
||||||
transformJourneysQuery: id,
|
transformJourneysQuery: id,
|
||||||
|
|
||||||
parseDateTime,
|
parseDateTime,
|
||||||
|
|
|
@ -7,15 +7,16 @@ const proxyAddress = typeof process !== 'undefined' && (process.env.HTTPS_PROXY
|
||||||
|
|
||||||
let getAgent = () => undefined;
|
let getAgent = () => undefined;
|
||||||
|
|
||||||
if (proxyAddress) {
|
const setupProxy = async () => {
|
||||||
import('https-proxy-agent').then(a => {
|
if (proxyAddress && !getAgent()) {
|
||||||
|
const a = await import('https-proxy-agent');
|
||||||
const agent = new a.default.HttpsProxyAgent(proxyAddress, {
|
const agent = new a.default.HttpsProxyAgent(proxyAddress, {
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 10 * 1000, // 10s
|
keepAliveMsecs: 10 * 1000, // 10s
|
||||||
});
|
});
|
||||||
getAgent = () => agent;
|
getAgent = () => agent;
|
||||||
});
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const randomBytesHexString = length => [...Array(length)].map(() => Math.floor(Math.random() * 16)
|
const randomBytesHexString = length => [...Array(length)].map(() => Math.floor(Math.random() * 16)
|
||||||
.toString(16))
|
.toString(16))
|
||||||
|
@ -66,14 +67,15 @@ const checkIfResponseIsOk = (_) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (body.fehlerNachricht) { // TODO better handling
|
if (body.fehlerNachricht || body.errors) { // TODO better handling
|
||||||
const {Error: HafasError, message, props} = getError(body);
|
const {Error: HafasError, message, props} = getError(body);
|
||||||
throw new HafasError(message, body.err, {...errProps, ...props});
|
throw new HafasError(message, body.err || body.errors, {...errProps, ...props});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = async (ctx, userAgent, reqData) => {
|
const request = async (ctx, userAgent, reqData) => {
|
||||||
const {profile, opt} = ctx;
|
const {profile, opt} = ctx;
|
||||||
|
await setupProxy();
|
||||||
|
|
||||||
const endpoint = reqData.endpoint;
|
const endpoint = reqData.endpoint;
|
||||||
delete reqData.endpoint;
|
delete reqData.endpoint;
|
||||||
|
@ -119,6 +121,7 @@ const request = async (ctx, userAgent, reqData) => {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// todo [breaking]: make this a FetchError or a HafasClientError?
|
// todo [breaking]: make this a FetchError or a HafasClientError?
|
||||||
|
console.log(JSON.stringify(res), await res.text());
|
||||||
const err = new Error(res.statusText);
|
const err = new Error(res.statusText);
|
||||||
Object.assign(err, errProps);
|
Object.assign(err, errProps);
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
@ -30,6 +30,7 @@ const tripEndpoint_dbnav = dbnavBase.tripEndpoint;
|
||||||
const tripEndpoint_dbregioguide = dbregioguideBase.tripEndpoint;
|
const tripEndpoint_dbregioguide = dbregioguideBase.tripEndpoint;
|
||||||
|
|
||||||
// arrivals(), departures()
|
// arrivals(), departures()
|
||||||
|
import {formatStationBoardReq} from '../dbregioguide/station-board-req.js';
|
||||||
const {boardEndpoint} = dbregioguideBase;
|
const {boardEndpoint} = dbregioguideBase;
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
|
@ -56,6 +57,7 @@ const profile = {
|
||||||
formatTripReq,
|
formatTripReq,
|
||||||
tripEndpoint_dbnav, tripEndpoint_dbregioguide,
|
tripEndpoint_dbnav, tripEndpoint_dbregioguide,
|
||||||
|
|
||||||
|
formatStationBoardReq,
|
||||||
boardEndpoint,
|
boardEndpoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
4
p/dbbahnhof/base.json
Normal file
4
p/dbbahnhof/base.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"boardEndpoint": "https://www.bahnhof.de/api/boards/",
|
||||||
|
"defaultLanguage": "en"
|
||||||
|
}
|
31
p/dbbahnhof/index.js
Normal file
31
p/dbbahnhof/index.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import baseProfile from './base.json' with { type: 'json' };
|
||||||
|
import {products} from '../../lib/products.js';
|
||||||
|
import {formatStationBoardReq} from './station-board-req.js';
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
...baseProfile,
|
||||||
|
locale: 'de-DE',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
|
||||||
|
products,
|
||||||
|
|
||||||
|
formatStationBoardReq,
|
||||||
|
|
||||||
|
journeysOutFrwd: false,
|
||||||
|
departuresGetPasslist: true,
|
||||||
|
departuresStbFltrEquiv: true,
|
||||||
|
trip: false,
|
||||||
|
radar: false,
|
||||||
|
refreshJourney: false,
|
||||||
|
journeysFromTrip: false,
|
||||||
|
refreshJourneyUseOutReconL: false,
|
||||||
|
tripsByName: false,
|
||||||
|
remarks: false,
|
||||||
|
remarksGetPolyline: false,
|
||||||
|
reachableFrom: false,
|
||||||
|
lines: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
profile,
|
||||||
|
};
|
30
p/dbbahnhof/station-board-req.js
Normal file
30
p/dbbahnhof/station-board-req.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import {stringify} from 'qs';
|
||||||
|
|
||||||
|
const formatStationBoardReq = (ctx, station, type) => {
|
||||||
|
const {profile, opt} = ctx;
|
||||||
|
|
||||||
|
if (opt.departure || opt.arrival) {
|
||||||
|
throw new Error('opt.departure/opt.arrival is not supported for profile dbbahnhof, can only query for current time.');
|
||||||
|
}
|
||||||
|
const evaNumbers = [station];
|
||||||
|
if (opt.moreStops) {
|
||||||
|
evaNumbers.push(...opt.moreStops);
|
||||||
|
}
|
||||||
|
const query = {
|
||||||
|
filterTransports: profile.formatProductsFilter(ctx, opt.products || {}, 'ris_alt'),
|
||||||
|
evaNumbers: evaNumbers,
|
||||||
|
duration: opt.duration,
|
||||||
|
sortBy: 'TIME_SCHEDULE',
|
||||||
|
locale: opt.language,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
endpoint: profile.boardEndpoint,
|
||||||
|
path: type + '?' + stringify(query, {arrayFormat: 'repeat', encodeValuesOnly: true}),
|
||||||
|
method: 'get',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
formatStationBoardReq,
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"journeysEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/fahrplan",
|
"journeysEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/fahrplan",
|
||||||
|
"bestpriceEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/tagesbestpreis",
|
||||||
"refreshJourneysEndpointTickets": "https://app.vendo.noncd.db.de/mob/angebote/recon",
|
"refreshJourneysEndpointTickets": "https://app.vendo.noncd.db.de/mob/angebote/recon",
|
||||||
"refreshJourneysEndpointPolyline": "https://app.vendo.noncd.db.de/mob/trip/recon",
|
"refreshJourneysEndpointPolyline": "https://app.vendo.noncd.db.de/mob/trip/recon",
|
||||||
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",
|
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",
|
||||||
|
|
|
@ -9,6 +9,10 @@ const formatBaseJourneysReq = (ctx) => {
|
||||||
einstiegsTypList: [
|
einstiegsTypList: [
|
||||||
'STANDARD',
|
'STANDARD',
|
||||||
],
|
],
|
||||||
|
fahrverguenstigungen: {
|
||||||
|
deutschlandTicketVorhanden: ctx.opt.deutschlandTicketDiscount,
|
||||||
|
nurDeutschlandTicketVerbindungen: ctx.opt.deutschlandTicketConnectionsOnly,
|
||||||
|
},
|
||||||
klasse: travellers.klasse,
|
klasse: travellers.klasse,
|
||||||
reisendenProfil: {
|
reisendenProfil: {
|
||||||
reisende: travellers.reisende.map(t => {
|
reisende: travellers.reisende.map(t => {
|
||||||
|
@ -55,8 +59,11 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
if (journeysRef) {
|
if (journeysRef) {
|
||||||
query.reiseHin.wunsch.context = journeysRef;
|
query.reiseHin.wunsch.context = journeysRef;
|
||||||
}
|
}
|
||||||
|
if (opt.notOnlyFastRoutes) {
|
||||||
|
query.reiseHin.wunsch.economic = true;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
endpoint: ctx.profile.journeysEndpoint,
|
endpoint: opt.bestprice ? profile.bestpriceEndpoint : profile.journeysEndpoint,
|
||||||
body: query,
|
body: query,
|
||||||
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v8+json'),
|
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v8+json'),
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import baseProfile from './base.json' with { type: 'json' };
|
import baseProfile from './base.json' with { type: 'json' };
|
||||||
import {products} from '../../lib/products.js';
|
import {products} from '../../lib/products.js';
|
||||||
import {formatTripReq} from './trip-req.js';
|
import {formatTripReq} from './trip-req.js';
|
||||||
|
import {formatStationBoardReq} from './station-board-req.js';
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
...baseProfile,
|
...baseProfile,
|
||||||
|
@ -9,6 +10,21 @@ const profile = {
|
||||||
|
|
||||||
products,
|
products,
|
||||||
formatTripReq,
|
formatTripReq,
|
||||||
|
formatStationBoardReq,
|
||||||
|
|
||||||
|
journeysOutFrwd: false,
|
||||||
|
departuresGetPasslist: false,
|
||||||
|
departuresStbFltrEquiv: true,
|
||||||
|
trip: false,
|
||||||
|
radar: false,
|
||||||
|
refreshJourney: false,
|
||||||
|
journeysFromTrip: false,
|
||||||
|
refreshJourneyUseOutReconL: false,
|
||||||
|
tripsByName: false,
|
||||||
|
remarks: false,
|
||||||
|
remarksGetPolyline: false,
|
||||||
|
reachableFrom: false,
|
||||||
|
lines: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
24
p/dbregioguide/station-board-req.js
Normal file
24
p/dbregioguide/station-board-req.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const formatStationBoardReq = (ctx, station, type) => {
|
||||||
|
const {profile, opt} = ctx;
|
||||||
|
|
||||||
|
if (opt.moreStops) {
|
||||||
|
station += ',' + opt.moreStops.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
endpoint: profile.boardEndpoint,
|
||||||
|
path: (type == 'departures' ? 'departure' : 'arrival') + '/' + station,
|
||||||
|
query: {
|
||||||
|
// TODO direction, fields below
|
||||||
|
modeOfTransport: profile.formatProductsFilter(ctx, opt.products || {}, 'ris'),
|
||||||
|
timeStart: profile.formatTime(profile, opt.when, true),
|
||||||
|
timeEnd: profile.formatTime(profile, opt.when.getTime() + opt.duration * 60 * 1000, true),
|
||||||
|
expandTimeFrame: 'TIME_END', // TODO impact?
|
||||||
|
},
|
||||||
|
method: 'get',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
formatStationBoardReq,
|
||||||
|
};
|
4
p/dbris/base.json
Normal file
4
p/dbris/base.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"boardEndpoint": "https://apis.deutschebahn.com/db/apis/ris-boards/v1/public/",
|
||||||
|
"defaultLanguage": "en"
|
||||||
|
}
|
31
p/dbris/index.js
Normal file
31
p/dbris/index.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import baseProfile from './base.json' with { type: 'json' };
|
||||||
|
import {products} from '../../lib/products.js';
|
||||||
|
import {formatStationBoardReq} from './station-board-req.js';
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
...baseProfile,
|
||||||
|
locale: 'de-DE',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
|
||||||
|
products,
|
||||||
|
|
||||||
|
formatStationBoardReq,
|
||||||
|
|
||||||
|
journeysOutFrwd: false,
|
||||||
|
departuresGetPasslist: true,
|
||||||
|
departuresStbFltrEquiv: true,
|
||||||
|
trip: false,
|
||||||
|
radar: false,
|
||||||
|
refreshJourney: false,
|
||||||
|
journeysFromTrip: false,
|
||||||
|
refreshJourneyUseOutReconL: false,
|
||||||
|
tripsByName: false,
|
||||||
|
remarks: false,
|
||||||
|
remarksGetPolyline: false,
|
||||||
|
reachableFrom: false,
|
||||||
|
lines: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
profile,
|
||||||
|
};
|
34
p/dbris/station-board-req.js
Normal file
34
p/dbris/station-board-req.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
const formatStationBoardReq = (ctx, station, type) => {
|
||||||
|
const {profile, opt} = ctx;
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
filterTransports: profile.formatProductsFilter(ctx, opt.products || {}, 'ris_alt'),
|
||||||
|
timeStart: profile.formatTime(profile, opt.when, true),
|
||||||
|
timeEnd: profile.formatTime(profile, opt.when.getTime() + opt.duration * 60 * 1000, true),
|
||||||
|
includeStationGroup: opt.includeRelatedStations,
|
||||||
|
maxTransportsPerType: opt.results === Infinity ? undefined : opt.results,
|
||||||
|
includeMessagesDisruptions: opt.remarks,
|
||||||
|
sortBy: 'TIME_SCHEDULE',
|
||||||
|
};
|
||||||
|
if (!opt.stopovers) {
|
||||||
|
query.maxViaStops = 0;
|
||||||
|
}
|
||||||
|
if (opt.moreStops) {
|
||||||
|
station += ',' + opt.moreStops.join(',');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
endpoint: profile.boardEndpoint,
|
||||||
|
path: type + '/' + station,
|
||||||
|
query: query,
|
||||||
|
method: 'get',
|
||||||
|
headers: {
|
||||||
|
'Db-Client-Id': process.env.DB_CLIENT_ID,
|
||||||
|
'Db-Api-Key': process.env.DB_API_KEY,
|
||||||
|
'Accept': 'application/vnd.de.db.ris+json',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
formatStationBoardReq,
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"journeysEndpoint": "https://int.bahn.de/web/api/angebote/fahrplan",
|
"journeysEndpoint": "https://int.bahn.de/web/api/angebote/fahrplan",
|
||||||
|
"bestpriceEndpoint": "https://int.bahn.de/web/api/angebote/tagesbestpreis",
|
||||||
"refreshJourneysEndpointTickets": "https://int.bahn.de/web/api/angebote/recon",
|
"refreshJourneysEndpointTickets": "https://int.bahn.de/web/api/angebote/recon",
|
||||||
"refreshJourneysEndpointPolyline": "https://int.bahn.de/web/api/reiseloesung/verbindung",
|
"refreshJourneysEndpointPolyline": "https://int.bahn.de/web/api/reiseloesung/verbindung",
|
||||||
"locationsEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte",
|
"locationsEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte",
|
||||||
|
|
|
@ -4,6 +4,8 @@ import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
|
||||||
import {formatLocationFilter} from './location-filter.js';
|
import {formatLocationFilter} from './location-filter.js';
|
||||||
import {formatLocationsReq} from './locations-req.js';
|
import {formatLocationsReq} from './locations-req.js';
|
||||||
import {formatStationBoardReq} from './station-board-req.js';
|
import {formatStationBoardReq} from './station-board-req.js';
|
||||||
|
import {formatTripReq} from './trip-req.js';
|
||||||
|
import {formatNearbyReq} from './nearby-req.js';
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
...baseProfile,
|
...baseProfile,
|
||||||
|
@ -14,9 +16,11 @@ const profile = {
|
||||||
|
|
||||||
formatJourneysReq,
|
formatJourneysReq,
|
||||||
formatRefreshJourneyReq,
|
formatRefreshJourneyReq,
|
||||||
|
formatTripReq,
|
||||||
|
formatNearbyReq,
|
||||||
formatLocationsReq,
|
formatLocationsReq,
|
||||||
formatLocationFilter,
|
|
||||||
formatStationBoardReq,
|
formatStationBoardReq,
|
||||||
|
formatLocationFilter,
|
||||||
|
|
||||||
departuresGetPasslist: true,
|
departuresGetPasslist: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,10 +10,10 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
let query = {
|
let query = {
|
||||||
maxUmstiege: transfers,
|
maxUmstiege: transfers,
|
||||||
minUmstiegszeit: opt.transferTime,
|
minUmstiegszeit: opt.transferTime,
|
||||||
deutschlandTicketVorhanden: false,
|
deutschlandTicketVorhanden: opt.deutschlandTicketDiscount,
|
||||||
nurDeutschlandTicketVerbindungen: false,
|
nurDeutschlandTicketVerbindungen: opt.deutschlandTicketConnectionsOnly,
|
||||||
reservierungsKontingenteVorhanden: false,
|
reservierungsKontingenteVorhanden: false,
|
||||||
schnelleVerbindungen: true,
|
schnelleVerbindungen: !opt.notOnlyFastRoutes,
|
||||||
sitzplatzOnly: false,
|
sitzplatzOnly: false,
|
||||||
abfahrtsHalt: from.lid,
|
abfahrtsHalt: from.lid,
|
||||||
zwischenhalte: opt.via
|
zwischenhalte: opt.via
|
||||||
|
@ -35,14 +35,14 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
if (opt.results !== null) {
|
if (opt.results !== null) {
|
||||||
// TODO query.numF = opt.results;
|
// TODO query.numF = opt.results;
|
||||||
}
|
}
|
||||||
query = Object.assign(query, ctx.profile.formatTravellers(ctx));
|
query = Object.assign(query, profile.formatTravellers(ctx));
|
||||||
return {
|
return {
|
||||||
endpoint: ctx.profile.journeysEndpoint,
|
endpoint: opt.bestprice ? profile.bestpriceEndpoint : profile.journeysEndpoint,
|
||||||
body: query,
|
body: query,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// TODO poly conditional other endpoint
|
|
||||||
const formatRefreshJourneyReq = (ctx, refreshToken) => {
|
const formatRefreshJourneyReq = (ctx, refreshToken) => {
|
||||||
const {profile, opt} = ctx;
|
const {profile, opt} = ctx;
|
||||||
if (opt.tickets) {
|
if (opt.tickets) {
|
||||||
|
|
2087
package-lock.json
generated
2087
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "db-vendo-client",
|
"name": "db-vendo-client",
|
||||||
"description": "Client for bahn.de public transport APIs.",
|
"description": "Client for bahn.de public transport APIs.",
|
||||||
"version": "6.6.1",
|
"version": "6.8.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"cross-fetch": "^4.1.0",
|
"cross-fetch": "^4.1.0",
|
||||||
"db-hafas-stations": "^1.0.0",
|
"db-hafas-stations": "2.0.0",
|
||||||
"gps-distance": "0.0.4",
|
"gps-distance": "0.0.4",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
|
|
|
@ -11,12 +11,12 @@ const createParseArrOrDep = (prefix) => {
|
||||||
const cancelled = profile.parseCancelled(d);
|
const cancelled = profile.parseCancelled(d);
|
||||||
const res = {
|
const res = {
|
||||||
tripId: d.journeyID || d.journeyId || d.train?.journeyId || d.zuglaufId,
|
tripId: d.journeyID || d.journeyId || d.train?.journeyId || d.zuglaufId,
|
||||||
stop: profile.parseLocation(ctx, d.station || d.abfrageOrt || {bahnhofsId: d.bahnhofsId}),
|
stop: profile.parseLocation(ctx, d.station || d.abfrageOrt || d.stopPlace || {bahnhofsId: d.bahnhofsId}),
|
||||||
...profile.parseWhen(
|
...profile.parseWhen(
|
||||||
ctx,
|
ctx,
|
||||||
null,
|
null,
|
||||||
d.timeSchedule || d.time || d.zeit || d.abgangsDatum || d.ankunftsDatum,
|
d.timeSchedule || d.time || d.zeit || d.abgangsDatum || d.ankunftsDatum,
|
||||||
d.timeType != 'SCHEDULE' ? d.timePredicted || d.time || d.ezZeit || d.ezAbgangsDatum || d.ezAnkunftsDatum : null,
|
d.timeType != 'SCHEDULE' ? d.timePredicted || d.timeDelayed || d.time || d.ezZeit || d.ezAbgangsDatum || d.ezAnkunftsDatum : null,
|
||||||
cancelled),
|
cancelled),
|
||||||
...profile.parsePlatform(ctx, d.platformSchedule || d.platform || d.gleis, d.platformPredicted || d.platform || d.ezGleis, cancelled),
|
...profile.parsePlatform(ctx, d.platformSchedule || d.platform || d.gleis, d.platformPredicted || d.platform || d.ezGleis, cancelled),
|
||||||
// prognosisType: TODO
|
// prognosisType: TODO
|
||||||
|
@ -40,14 +40,21 @@ const createParseArrOrDep = (prefix) => {
|
||||||
res.remarks = profile.parseRemarks(ctx, d);
|
res.remarks = profile.parseRemarks(ctx, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((opt.stopovers || opt.direction) && Array.isArray(d.ueber)) {
|
if (opt.stopovers || opt.direction) {
|
||||||
const stopovers = d.ueber
|
let stopovers = undefined;
|
||||||
.map(viaName => profile.parseStopover(ctx, {name: viaName}, null));
|
if (Array.isArray(d.ueber)) {
|
||||||
|
stopovers = d.ueber
|
||||||
if (prefix === ARRIVAL) {
|
.map(viaName => profile.parseStopover(ctx, {name: viaName}, null));
|
||||||
res.previousStopovers = stopovers;
|
} else if (Array.isArray(d.transport?.via) || Array.isArray(d.viaStops)) {
|
||||||
} else if (prefix === DEPARTURE) {
|
stopovers = (d.transport?.via || d.viaStops)
|
||||||
res.nextStopovers = stopovers;
|
.map(via => profile.parseStopover(ctx, via, null));
|
||||||
|
}
|
||||||
|
if (stopovers) {
|
||||||
|
if (prefix === ARRIVAL) {
|
||||||
|
res.previousStopovers = stopovers;
|
||||||
|
} else if (prefix === DEPARTURE) {
|
||||||
|
res.nextStopovers = stopovers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,15 @@ const parseJourney = (ctx, jj) => { // 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);
|
// TODO parse scheduledDays as before
|
||||||
|
res.serviceDays = j.serviceDays.map(d => ({
|
||||||
|
irregular: d.irregular,
|
||||||
|
lastDateInPeriod: d.lastDateInPeriod || d.letztesDatumInZeitraum,
|
||||||
|
planningPeriodBegin: d.planningPeriodBegin || d.planungsZeitraumAnfang,
|
||||||
|
planningPeriodEnd: d.planningPeriodEnd || d.planungsZeitraumEnde,
|
||||||
|
regular: d.regular,
|
||||||
|
weekdays: d.weekdays || d.wochentage,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.price = profile.parsePrice(ctx, jj);
|
res.price = profile.parsePrice(ctx, jj);
|
||||||
|
|
|
@ -2,20 +2,20 @@ import slugg from 'slugg';
|
||||||
|
|
||||||
const parseLine = (ctx, p) => {
|
const parseLine = (ctx, p) => {
|
||||||
const profile = ctx.profile;
|
const profile = ctx.profile;
|
||||||
const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no || p.no || ((p.risZuglaufId || '') + '_').split('_')[1] || p.verkehrsmittelNummer || (p.verkehrmittel?.langText || p.verkehrsmittel?.langText || p.mitteltext || p.zugName || '').replace(/\D/g, '');
|
const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no || p.no || ((p.risZuglaufId || '') + '_').split('_')[1] || p.verkehrsmittelNummer || (p.verkehrmittel?.langText || p.verkehrsmittel?.langText || p.mitteltext || p.zugName || p.lineName || '').replace(/\D/g, '');
|
||||||
const res = {
|
const res = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
id: slugg(p.verkehrsmittel?.langText || p.verkehrmittel?.langText || p.transport?.journeyDescription || p.risZuglaufId || p.train && p.train.category + ' ' + p.train.lineName + ' ' + p.train.no || p.no && p.name + ' ' + p.no || p.langtext || p.mitteltext || p.zugName), // TODO terrible
|
id: slugg(p.verkehrsmittel?.langText || p.verkehrmittel?.langText || p.transport?.journeyDescription || p.risZuglaufId || p.train && p.train.category + ' ' + p.train.lineName + ' ' + p.train.no || p.no && p.name + ' ' + p.no || p.langtext || p.mitteltext || p.zugName || p.lineName), // TODO terrible
|
||||||
fahrtNr: String(fahrtNr),
|
fahrtNr: String(fahrtNr),
|
||||||
name: p.verkehrsmittel?.name || p.verkehrsmittel?.langText || p.verkehrmittel?.name || p.verkehrmittel?.langText || p.zugName || p.transport && p.transport.category + ' ' + p.transport.line || p.train && p.train.category + ' ' + p.train.lineName || p.name || p.mitteltext || p.langtext,
|
name: p.verkehrsmittel?.name || p.verkehrsmittel?.langText || p.verkehrmittel?.name || p.verkehrmittel?.langText || p.zugName || p.transport && p.transport.category + ' ' + p.transport.line || p.train && p.train.category + ' ' + p.train.lineName || p.name || p.mitteltext || p.langtext || p.lineName,
|
||||||
public: true,
|
public: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const adminCode = p.administrationId || p.administration?.id || p.administration?.administrationID;
|
const adminCode = p.administrationID || p.administrationId || p.administration?.id || p.administration?.administrationID;
|
||||||
if (adminCode) {
|
if (adminCode) {
|
||||||
res.adminCode = adminCode;
|
res.adminCode = adminCode;
|
||||||
}
|
}
|
||||||
res.productName = p.verkehrsmittel?.kurzText || p.verkehrmittel?.kurzText || p.transport?.category || p.train?.category || p.category || p.kurztext;
|
res.productName = p.verkehrsmittel?.kurzText || p.verkehrmittel?.kurzText || p.transport?.category || p.train?.category || p.category || p.kurztext || p.lineName?.replace(/\d/g, '');
|
||||||
const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.vendo == p.verkehrmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type || pp.ris == p.type || pp.ris_alt == p.train?.type || pp.ris_alt == p.type || pp.dbnav_short == p.produktGattung);
|
const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.vendo == p.verkehrmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type || pp.ris == p.type || pp.ris_alt == p.train?.type || pp.ris_alt == p.type || pp.dbnav_short == p.produktGattung);
|
||||||
res.mode = foundProduct?.mode;
|
res.mode = foundProduct?.mode;
|
||||||
res.product = foundProduct?.id;
|
res.product = foundProduct?.id;
|
||||||
|
|
|
@ -9,7 +9,7 @@ const parseLoadFactor = (opt, auslastung) => {
|
||||||
if (!auslastung) {
|
if (!auslastung) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const cls = opt.firstClass
|
const cls = opt.firstClass === true
|
||||||
? 'KLASSE_1'
|
? 'KLASSE_1'
|
||||||
: 'KLASSE_2';
|
: 'KLASSE_2';
|
||||||
const load = auslastung.find(a => a.klasse === cls)?.stufe;
|
const load = auslastung.find(a => a.klasse === cls)?.stufe;
|
||||||
|
|
|
@ -7,7 +7,7 @@ const ADDRESS = 'ADR';
|
||||||
const leadingZeros = /^0+/;
|
const leadingZeros = /^0+/;
|
||||||
|
|
||||||
const parseLocation = (ctx, l) => {
|
const parseLocation = (ctx, l) => {
|
||||||
const {profile, common} = ctx;
|
const {profile} = ctx;
|
||||||
|
|
||||||
if (!l) {
|
if (!l) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -53,16 +53,6 @@ const parseLocation = (ctx, l) => {
|
||||||
return stop;
|
return stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name && common?.locations?.[name] && res.id === null) {
|
|
||||||
delete res.type;
|
|
||||||
delete res.id;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...common.locations[name],
|
|
||||||
...res,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
res.name = name;
|
res.name = name;
|
||||||
res = profile.enrichStation(ctx, res);
|
res = profile.enrichStation(ctx, res);
|
||||||
|
|
||||||
|
@ -87,6 +77,15 @@ const enrichStation = (ctx, stop, locations) => {
|
||||||
...rich,
|
...rich,
|
||||||
...stop,
|
...stop,
|
||||||
};
|
};
|
||||||
|
delete stop.lines;
|
||||||
|
delete stop.facilities;
|
||||||
|
delete stop.reisezentrumOpeningHours;
|
||||||
|
if (stop.station) {
|
||||||
|
stop.station = {...stop.station};
|
||||||
|
delete stop.station.lines;
|
||||||
|
delete stop.station.facilities;
|
||||||
|
delete stop.station.reisezentrumOpeningHours;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return stop;
|
return stop;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,12 @@ const parseRemarks = (ctx, ref) => {
|
||||||
ref.himNotizen || [],
|
ref.himNotizen || [],
|
||||||
ref.hims || [],
|
ref.hims || [],
|
||||||
ref.serviceNotiz && [ref.serviceNotiz] || [],
|
ref.serviceNotiz && [ref.serviceNotiz] || [],
|
||||||
ref.messages || [],
|
ref.messages?.common || [],
|
||||||
|
ref.messages?.delay || [],
|
||||||
|
ref.messages?.cancelation || [],
|
||||||
|
ref.messages?.destination || [],
|
||||||
|
ref.messages?.via || [],
|
||||||
|
Array.isArray(ref.messages) ? ref.messages : [],
|
||||||
ref.meldungen || [],
|
ref.meldungen || [],
|
||||||
ref.meldungenAsObject || [],
|
ref.meldungenAsObject || [],
|
||||||
ref.attributNotizen || [],
|
ref.attributNotizen || [],
|
||||||
|
@ -28,12 +33,12 @@ const parseRemarks = (ctx, ref) => {
|
||||||
if (remark.prioritaet || remark.prio || remark.type) {
|
if (remark.prioritaet || remark.prio || remark.type) {
|
||||||
type = 'status';
|
type = 'status';
|
||||||
}
|
}
|
||||||
if (!remark.priority && !remark.kategorie && remark.key || remark.disruptionID
|
if (!remark.priority && !remark.kategorie && remark.key || remark.disruptionID || remark.important
|
||||||
|| remark.prioritaet && remark.prioritaet == 'HOCH' || remark.prio && remark.prio == 'HOCH' || remark.priority && remark.priority < 100) {
|
|| 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 || remark.id,
|
code: remark.code || remark.key || remark.id || remark.type,
|
||||||
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || remark.shortText
|
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || remark.shortText
|
||||||
|| Object.values(remark.descriptions || {})
|
|| Object.values(remark.descriptions || {})
|
||||||
.shift()?.textShort,
|
.shift()?.textShort,
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
const PARTIAL_FARE_HINT = 'Teilpreis / partial fare';
|
||||||
|
|
||||||
const parsePrice = (ctx, raw) => {
|
const parsePrice = (ctx, raw) => {
|
||||||
const p = raw.angebotsPreis || raw.angebote?.preise?.gesamt?.ab; // TODO teilpreis
|
const p = raw.angebotsPreis || raw.angebote?.preise?.gesamt?.ab || raw.abPreis;
|
||||||
if (p?.betrag) {
|
if (p?.betrag) {
|
||||||
|
const partialFare = raw.hasTeilpreis ?? raw.angebote?.preise?.istTeilpreis ?? raw.teilpreis;
|
||||||
return {
|
return {
|
||||||
amount: p.betrag,
|
amount: p.betrag,
|
||||||
currency: p.waehrung,
|
currency: p.waehrung,
|
||||||
hint: null,
|
hint: partialFare ? PARTIAL_FARE_HINT : null,
|
||||||
|
partialFare: partialFare,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -45,7 +48,7 @@ const parseTickets = (ctx, j) => {
|
||||||
partialFare: s.teilpreis,
|
partialFare: s.teilpreis,
|
||||||
};
|
};
|
||||||
if (s.teilpreis) {
|
if (s.teilpreis) {
|
||||||
p.addData = 'Teilpreis / partial fare';
|
p.addData = PARTIAL_FARE_HINT;
|
||||||
}
|
}
|
||||||
const conds = s.konditionsAnzeigen || s.konditionen;
|
const conds = s.konditionsAnzeigen || s.konditionen;
|
||||||
if (conds) {
|
if (conds) {
|
||||||
|
|
42
readme.md
42
readme.md
|
@ -6,16 +6,16 @@
|
||||||

|

|
||||||
[](https://github.com/sponsors/derhuerst)
|
[](https://github.com/sponsors/derhuerst)
|
||||||
|
|
||||||
This is an early version. What works:
|
The following [FPTF](https://github.com/public-transport/friendly-public-transport-format)/[hafas-client](https://github.com/public-transport/hafas-client/) endpoints are supported (depending on the chosen profile, see below):
|
||||||
|
|
||||||
* `journeys()`, `refreshJourney()` including tickets
|
* `journeys()`, `refreshJourney()` including tickets and bestprice option
|
||||||
* `locations()`, `nearby()`,
|
* `locations()`, `nearby()`,
|
||||||
* `departures()`, `arrivals()` boards
|
* `departures()`, `arrivals()` boards
|
||||||
* `trip()`
|
* `trip()`
|
||||||
|
|
||||||
What doesn't work:
|
What doesn't work:
|
||||||
|
|
||||||
* `journeys()` details like scheduledDays, stop/station groups, some line details ...
|
* `journeys()` details like stop/station groups, some line details ...
|
||||||
* loadFactor and other details in boards
|
* loadFactor and other details in boards
|
||||||
* certain stop details like products for `locations()` and geopositions for boards – this can be remedied with `enrichStations` in the config (turned on by default), enriching stop info with [db-hafas-stations](https://github.com/derhuerst/db-hafas-stations).
|
* certain stop details like products for `locations()` and geopositions for boards – this can be remedied with `enrichStations` in the config (turned on by default), enriching stop info with [db-hafas-stations](https://github.com/derhuerst/db-hafas-stations).
|
||||||
* some query options/filters (e.g. routingMode for journeys, direction for boards)
|
* some query options/filters (e.g. routingMode for journeys, direction for boards)
|
||||||
|
@ -23,21 +23,25 @@ What doesn't work:
|
||||||
|
|
||||||
Depending on the configured profile, db-vendo-client will use multiple different DB APIs that offer varying functionality, so choose wisely:
|
Depending on the configured profile, db-vendo-client will use multiple different DB APIs that offer varying functionality, so choose wisely:
|
||||||
|
|
||||||
| | `db` Profile | `dbnav` Profile | `dbweb` Profile
|
| Profile | `db` | `dbnav` | `dbweb` | `dbbahnhof` | `dbris` |
|
||||||
| ------------- | ------------- | ------------- | ------------- |
|
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||||
| no API key required | ✅ | ✅ | ✅ |
|
| no API key required | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||||
| max duration boards | 12h | 1h | 1h |
|
| all above endpoints supported | ✅ | ✅ | except `stop()` | only boards | only boards |
|
||||||
| remarks | not for boards | for boards only most important remarks | all remarks on boards and journeys |
|
| duration for boards | up to 12h | always 1h | always 1h | up to 6h, only from current time | up to 12h |
|
||||||
| cancelled trips | contained with cancelled flag in journeys, not contained in boards | contained with cancelled flag | contained with cancelled flag |
|
| remarks | not for boards | for boards only most important remarks | all remarks on boards and journeys | most remarks | all remarks |
|
||||||
| tickets | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines |
|
| cancelled trips | contained with cancelled flag in journeys, not contained in boards | contained with cancelled flag | contained with cancelled flag | contained with cancelled flag | contained with cancelled flag |
|
||||||
| polylines | only for `refreshJourney()` (mutually exclusive with tickets) and for `trip()` (only for HAFAS trip ids) | only for `refreshJourney()/trip()`, mutually exclusive with tickets | only for `refreshJourney()/trip()`, mutually exclusive with tickets |
|
| tickets | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | ❌ | ❌ |
|
||||||
| trip ids used | HAFAS trip ids for journeys, RIS trip ids for boards (static on train splits?) | HAFAS trip ids | HAFAS trip ids |
|
| polylines | only for `refreshJourney()` (mutually exclusive with tickets) and for `trip()` (only for HAFAS trip ids) | only for `refreshJourney()/trip()`, mutually exclusive with tickets | only for `refreshJourney()/trip()`, mutually exclusive with tickets | ❌ | ❌ |
|
||||||
| line.id/fahrtNr used | actual fahrtNr | actual fahrtNr for journeys, unreliable/route id for boards and `trip()` | unreliable/route id |
|
| trip ids used | HAFAS trip ids for journeys, RIS trip ids for boards (static on train splits?) | HAFAS trip ids | HAFAS trip ids | RIS trip ids | RIS trip ids |
|
||||||
| adminCode/operator | ✅ | only for journeys | only operator |
|
| line.id/fahrtNr used | actual fahrtNr | actual fahrtNr for journeys, unreliable/route id for boards and `trip()` | unreliable/route id | unreliable/route id | ✅ |
|
||||||
| stopovers | not in boards | not in boards | ✅ |
|
| adminCode/operator | ✅ | only for journeys | only operator | only adminCode | ✅ |
|
||||||
| `stop()` | ✅ | ✅ | ❌ |
|
| stopovers | not in boards | not in boards | ✅ | some | ✅ |
|
||||||
| assumed backend API stability | less stable | more stable | less stable |
|
| assumed backend API stability | less stable | more stable | less stable | less stable | more stable |
|
||||||
| quotas | 60 requests per minute for journeys, unknown for boards (IPv4) | 60 requests per minute (IPv4) | ? (IPv6) |
|
| quotas | 60 requests per minute for journeys, unknown for boards (IPv4) | 60 requests per minute (IPv4) | aggressive blocking (IPv4/IPv6) | ? | depends on API key |
|
||||||
|
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> If you think that for your project, quotas may become an issue, [consider alternative ways to obtain the data you need.](https://github.com/derhuerst/db-rest/blob/6/docs/readme.md#why-not-to-use-this-api).
|
||||||
|
|
||||||
Feel free to report anything that you stumble upon via Issues or create a PR :)
|
Feel free to report anything that you stumble upon via Issues or create a PR :)
|
||||||
|
|
||||||
|
@ -81,7 +85,7 @@ There are [community-maintained TypeScript typings available as `@types/hafas-cl
|
||||||
|
|
||||||
`db-vendo-client` is mostly browser compatible, however none of the endpoints enables CORS, so it is impossible to use `db-vendo-client` in normal browser environments. It was tested with vite + capacitorjs and should also work with cordova or react native and similar projects.
|
`db-vendo-client` is mostly browser compatible, however none of the endpoints enables CORS, so it is impossible to use `db-vendo-client` in normal browser environments. It was tested with vite + capacitorjs and should also work with cordova or react native and similar projects.
|
||||||
|
|
||||||
**Limitations:** Does not work with `enrichStations` option enabled.
|
**Limitations:** Does not work with `enrichStations` option enabled or with the `dbris` profile.
|
||||||
|
|
||||||
## Related Projects
|
## Related Projects
|
||||||
|
|
||||||
|
|
29
test/dbbahnhof-departures.js
Normal file
29
test/dbbahnhof-departures.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
|
import {createClient} from '../index.js';
|
||||||
|
import {profile as rawProfile} from '../p/dbbahnhof/index.js';
|
||||||
|
import res from './fixtures/dbbahnhof-departures.json' with { type: 'json' };
|
||||||
|
import {dbDepartures as expected} from './fixtures/dbbahnhof-departures.js';
|
||||||
|
|
||||||
|
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||||
|
const {profile} = client;
|
||||||
|
|
||||||
|
const opt = {
|
||||||
|
direction: null,
|
||||||
|
duration: 10,
|
||||||
|
linesOfStops: true,
|
||||||
|
remarks: true,
|
||||||
|
stopovers: false,
|
||||||
|
includeRelatedStations: true,
|
||||||
|
when: '2019-08-19T20:30:00+02:00',
|
||||||
|
products: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('parses a bahnhof.de departure correctly', (t) => {
|
||||||
|
const ctx = {profile, opt, common: null, res};
|
||||||
|
const arrivals = res.entries.flat()
|
||||||
|
.map(d => profile.parseArrival(ctx, d));
|
||||||
|
|
||||||
|
t.same(arrivals, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
import tap from 'tap';
|
import tap from 'tap';
|
||||||
|
|
||||||
import {createClient} from '../index.js';
|
import {createClient} from '../index.js';
|
||||||
import {profile as rawProfile} from '../p/dbweb/index.js';
|
import {profile as rawProfile} from '../p/dbris/index.js';
|
||||||
import res from './fixtures/dbris-arrivals.json' with { type: 'json' };
|
import res from './fixtures/dbris-arrivals.json' with { type: 'json' };
|
||||||
import {dbArrivals as expected} from './fixtures/dbris-arrivals.js';
|
import {dbArrivals as expected} from './fixtures/dbris-arrivals.js';
|
||||||
|
|
||||||
|
|
28
test/dbris-departures.js
Normal file
28
test/dbris-departures.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
|
import {createClient} from '../index.js';
|
||||||
|
import {profile as rawProfile} from '../p/dbris/index.js';
|
||||||
|
import res from './fixtures/dbris-departures.json' with { type: 'json' };
|
||||||
|
import {dbDepartures as expected} from './fixtures/dbris-departures.js';
|
||||||
|
|
||||||
|
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||||
|
const {profile} = client;
|
||||||
|
|
||||||
|
const opt = {
|
||||||
|
direction: null,
|
||||||
|
duration: 10,
|
||||||
|
linesOfStops: true,
|
||||||
|
remarks: true,
|
||||||
|
stopovers: false,
|
||||||
|
includeRelatedStations: true,
|
||||||
|
when: '2019-08-19T20:30:00+02:00',
|
||||||
|
products: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('parses a RIS::Boards departure correctly', (t) => {
|
||||||
|
const ctx = {profile, opt, common: null, res};
|
||||||
|
const arrivals = res.departures.map(d => profile.parseArrival(ctx, d));
|
||||||
|
|
||||||
|
t.same(arrivals, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
237
test/e2e/db.js
237
test/e2e/db.js
|
@ -99,138 +99,139 @@ const potsdamHbf = '8012666';
|
||||||
const berlinSüdkreuz = '8011113';
|
const berlinSüdkreuz = '8011113';
|
||||||
const kölnHbf = '8000207';
|
const kölnHbf = '8000207';
|
||||||
|
|
||||||
tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
if (!process.env.VCR_OFF) {
|
||||||
const res = await client.journeys(blnSchwedterStr, münchenHbf, {
|
tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
||||||
results: 4,
|
const res = await client.journeys(blnSchwedterStr, münchenHbf, {
|
||||||
departure: when,
|
results: 4,
|
||||||
stopovers: true,
|
departure: when,
|
||||||
});
|
stopovers: true,
|
||||||
|
});
|
||||||
|
|
||||||
await testJourneysStationToStation({
|
await testJourneysStationToStation({
|
||||||
test: t,
|
test: t,
|
||||||
res,
|
res,
|
||||||
validate,
|
validate,
|
||||||
fromId: blnSchwedterStr,
|
fromId: blnSchwedterStr,
|
||||||
toId: münchenHbf,
|
toId: münchenHbf,
|
||||||
});
|
});
|
||||||
// todo: find a journey where there pricing info is always available
|
// todo: find a journey where there pricing info is always available
|
||||||
for (let journey of res.journeys) {
|
for (let journey of res.journeys) {
|
||||||
if (journey.price) {
|
if (journey.price) {
|
||||||
assertValidPrice(t, journey.price);
|
assertValidPrice(t, journey.price);
|
||||||
|
}
|
||||||
|
if (journey.tickets) {
|
||||||
|
assertValidTickets(t, journey.tickets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (journey.tickets) {
|
t.end();
|
||||||
assertValidTickets(t, journey.tickets);
|
});
|
||||||
|
|
||||||
|
tap.test('refreshJourney – valid tickets', async (t) => {
|
||||||
|
const T_MOCK = 1758279600 * 1000;
|
||||||
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
|
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
||||||
|
results: 4,
|
||||||
|
departure: when,
|
||||||
|
stopovers: true,
|
||||||
|
});
|
||||||
|
const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
||||||
|
tickets: false,
|
||||||
|
});
|
||||||
|
const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
||||||
|
tickets: true,
|
||||||
|
});
|
||||||
|
for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) {
|
||||||
|
if (res.journey.tickets !== undefined) {
|
||||||
|
assertValidTickets(t, res.journey.tickets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('refreshJourney – valid tickets', async (t) => {
|
t.end();
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
|
|
||||||
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
|
||||||
results: 4,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
|
||||||
tickets: false,
|
|
||||||
});
|
|
||||||
const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
|
||||||
tickets: true,
|
|
||||||
});
|
|
||||||
for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) {
|
|
||||||
if (res.journey.tickets !== undefined) {
|
|
||||||
assertValidTickets(t, res.journey.tickets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: journeys, only one product
|
|
||||||
|
|
||||||
tap.test('journeys – fails with no product', async (t) => {
|
|
||||||
await journeysFailsWithNoProduct({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
products: dbProfile.products,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
|
||||||
const torfstr = {
|
|
||||||
type: 'location',
|
|
||||||
address: 'Torfstraße 17',
|
|
||||||
latitude: 52.5416823,
|
|
||||||
longitude: 13.3491223,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await testJourneysStationToAddress({
|
// todo: journeys, only one product
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: torfstr,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
tap.test('journeys – fails with no product', async (t) => {
|
||||||
const atze = {
|
await journeysFailsWithNoProduct({
|
||||||
type: 'location',
|
test: t,
|
||||||
id: '991598902',
|
fetchJourneys: client.journeys,
|
||||||
poi: true,
|
fromId: blnSchwedterStr,
|
||||||
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
toId: münchenHbf,
|
||||||
latitude: 52.542417,
|
when,
|
||||||
longitude: 13.350437,
|
products: dbProfile.products,
|
||||||
};
|
});
|
||||||
const res = await client.journeys(blnSchwedterStr, atze, {
|
t.end();
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await testJourneysStationToPoi({
|
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
||||||
test: t,
|
const torfstr = {
|
||||||
res,
|
type: 'location',
|
||||||
validate,
|
address: 'Torfstraße 17',
|
||||||
fromId: blnSchwedterStr,
|
latitude: 52.5416823,
|
||||||
to: atze,
|
longitude: 13.3491223,
|
||||||
});
|
};
|
||||||
t.end();
|
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
||||||
});
|
results: 3,
|
||||||
|
departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
tap.test('journeys: via works – with detour', async (t) => {
|
await testJourneysStationToAddress({
|
||||||
// Going from Westhafen to Wedding via Württembergallee without detour
|
test: t,
|
||||||
// is currently impossible. We check if the routing engine computes a detour.
|
res,
|
||||||
const res = await client.journeys(westhafen, wedding, {
|
validate,
|
||||||
via: württembergallee,
|
fromId: blnSchwedterStr,
|
||||||
results: 1,
|
to: torfstr,
|
||||||
departure: when,
|
});
|
||||||
stopovers: true,
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
await testJourneysWithDetour({
|
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
||||||
test: t,
|
const atze = {
|
||||||
res,
|
type: 'location',
|
||||||
validate,
|
id: '991598902',
|
||||||
detourIds: [württembergallee],
|
poi: true,
|
||||||
|
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
||||||
|
latitude: 52.542417,
|
||||||
|
longitude: 13.350437,
|
||||||
|
};
|
||||||
|
const res = await client.journeys(blnSchwedterStr, atze, {
|
||||||
|
results: 3,
|
||||||
|
departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysStationToPoi({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
fromId: blnSchwedterStr,
|
||||||
|
to: atze,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
});
|
});
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
tap.test('journeys: via works – with detour', async (t) => {
|
||||||
// todo: without detour
|
// Going from Westhafen to Wedding via Württembergallee without detour
|
||||||
|
// is currently impossible. We check if the routing engine computes a detour.
|
||||||
|
const res = await client.journeys(westhafen, wedding, {
|
||||||
|
via: württembergallee,
|
||||||
|
results: 1,
|
||||||
|
departure: when,
|
||||||
|
stopovers: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysWithDetour({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
detourIds: [württembergallee],
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
||||||
|
// todo: without detour
|
||||||
|
}
|
||||||
|
|
||||||
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
||||||
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
||||||
|
@ -260,7 +261,7 @@ if (!process.env.VCR_MODE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tap.test('refreshJourney', async (t) => {
|
tap.test('refreshJourney', async (t) => {
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
const T_MOCK = 1763542800 * 1000;
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
const validate = createValidate({...cfg, when});
|
const validate = createValidate({...cfg, when});
|
||||||
|
|
||||||
|
|
146
test/e2e/dbbahnhof.js
Normal file
146
test/e2e/dbbahnhof.js
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import tap from 'tap';
|
||||||
|
import isRoughlyEqual from 'is-roughly-equal';
|
||||||
|
|
||||||
|
import {createWhen} from './lib/util.js';
|
||||||
|
import {createClient} from '../../index.js';
|
||||||
|
import {profile as dbProfile} from '../../p/dbregioguide/index.js';
|
||||||
|
import {
|
||||||
|
createValidateStation,
|
||||||
|
createValidateTrip,
|
||||||
|
} from './lib/validators.js';
|
||||||
|
import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js';
|
||||||
|
import {testJourneysStationToStation} from './lib/journeys-station-to-station.js';
|
||||||
|
import {testJourneysStationToAddress} from './lib/journeys-station-to-address.js';
|
||||||
|
import {testJourneysStationToPoi} from './lib/journeys-station-to-poi.js';
|
||||||
|
import {testEarlierLaterJourneys} from './lib/earlier-later-journeys.js';
|
||||||
|
import {testLegCycleAlternatives} from './lib/leg-cycle-alternatives.js';
|
||||||
|
import {testRefreshJourney} from './lib/refresh-journey.js';
|
||||||
|
import {journeysFailsWithNoProduct} from './lib/journeys-fails-with-no-product.js';
|
||||||
|
import {testDepartures} from './lib/departures.js';
|
||||||
|
import {testArrivals} from './lib/arrivals.js';
|
||||||
|
import {testJourneysWithDetour} from './lib/journeys-with-detour.js';
|
||||||
|
|
||||||
|
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
||||||
|
const minute = 60 * 1000;
|
||||||
|
|
||||||
|
const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00
|
||||||
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
|
const cfg = {
|
||||||
|
when,
|
||||||
|
stationCoordsOptional: true, // TODO
|
||||||
|
products: dbProfile.products,
|
||||||
|
minLatitude: 46.673100,
|
||||||
|
maxLatitude: 55.030671,
|
||||||
|
minLongitude: 6.896517,
|
||||||
|
maxLongitude: 16.180237,
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = createValidate(cfg);
|
||||||
|
|
||||||
|
const assertValidPrice = (t, p) => {
|
||||||
|
t.ok(p);
|
||||||
|
if (p.amount !== null) {
|
||||||
|
t.equal(typeof p.amount, 'number');
|
||||||
|
t.ok(p.amount > 0);
|
||||||
|
}
|
||||||
|
if (p.hint !== null) {
|
||||||
|
t.equal(typeof p.hint, 'string');
|
||||||
|
t.ok(p.hint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertValidTickets = (test, tickets) => {
|
||||||
|
test.ok(Array.isArray(tickets));
|
||||||
|
for (let fare of tickets) {
|
||||||
|
test.equal(typeof fare.name, 'string', 'Mandatory field "name" is missing or not a string');
|
||||||
|
test.ok(fare.name);
|
||||||
|
|
||||||
|
test.ok(isObj(fare.priceObj), 'Mandatory field "priceObj" is missing or not an object');
|
||||||
|
test.equal(typeof fare.priceObj.amount, 'number', 'Mandatory field "amount" in "priceObj" is missing or not a number');
|
||||||
|
test.ok(fare.priceObj.amount > 0);
|
||||||
|
if ('currency' in fare.priceObj) {
|
||||||
|
test.equal(typeof fare.priceObj.currency, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check optional fields
|
||||||
|
if ('addData' in fare) {
|
||||||
|
test.equal(typeof fare.addData, 'string');
|
||||||
|
}
|
||||||
|
if ('addDataTicketInfo' in fare) {
|
||||||
|
test.equal(typeof fare.addDataTicketInfo, 'string');
|
||||||
|
}
|
||||||
|
if ('addDataTicketDetails' in fare) {
|
||||||
|
test.equal(typeof fare.addDataTicketDetails, 'string');
|
||||||
|
}
|
||||||
|
if ('addDataTravelInfo' in fare) {
|
||||||
|
test.equal(typeof fare.addDataTravelInfo, 'string');
|
||||||
|
}
|
||||||
|
if ('addDataTravelDetails' in fare) {
|
||||||
|
test.equal(typeof fare.firstClass, 'boolean');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = createClient(dbProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||||
|
|
||||||
|
const berlinHbf = '8011160';
|
||||||
|
const münchenHbf = '8000261';
|
||||||
|
const jungfernheide = '8011167';
|
||||||
|
const blnSchwedterStr = '732652';
|
||||||
|
const westhafen = '8089116';
|
||||||
|
const wedding = '8089131';
|
||||||
|
const württembergallee = '731084';
|
||||||
|
const regensburgHbf = '8000309';
|
||||||
|
const blnOstbahnhof = '8010255';
|
||||||
|
const blnTiergarten = '8089091';
|
||||||
|
const blnJannowitzbrücke = '8089019';
|
||||||
|
const potsdamHbf = '8012666';
|
||||||
|
const berlinSüdkreuz = '8011113';
|
||||||
|
const kölnHbf = '8000207';
|
||||||
|
|
||||||
|
|
||||||
|
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||||
|
const res = await client.departures(blnSchwedterStr, {
|
||||||
|
duration: 5, when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testDepartures({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
id: blnSchwedterStr,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('departures with station object', async (t) => {
|
||||||
|
const res = await client.departures({
|
||||||
|
type: 'station',
|
||||||
|
id: jungfernheide,
|
||||||
|
name: 'Berlin Jungfernheide',
|
||||||
|
location: {
|
||||||
|
type: 'location',
|
||||||
|
latitude: 1.23,
|
||||||
|
longitude: 2.34,
|
||||||
|
},
|
||||||
|
}, {when});
|
||||||
|
|
||||||
|
validate(t, res, 'departuresResponse', 'res');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
||||||
|
const res = await client.arrivals(blnSchwedterStr, {
|
||||||
|
duration: 5, when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testArrivals({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
id: blnSchwedterStr,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
|
@ -126,7 +126,7 @@ tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('refreshJourney – valid tickets', async (t) => {
|
tap.test('refreshJourney – valid tickets', async (t) => {
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
const T_MOCK = 1758279600 * 1000;
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
||||||
|
@ -151,131 +151,133 @@ tap.test('refreshJourney – valid tickets', async (t) => {
|
||||||
|
|
||||||
// todo: journeys, only one product
|
// todo: journeys, only one product
|
||||||
|
|
||||||
tap.test('journeys – fails with no product', async (t) => {
|
if (!process.env.VCR_OFF) {
|
||||||
await journeysFailsWithNoProduct({
|
tap.test('journeys – fails with no product', async (t) => {
|
||||||
test: t,
|
await journeysFailsWithNoProduct({
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
products: dbProfile.products,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
|
||||||
const torfstr = {
|
|
||||||
type: 'location',
|
|
||||||
address: 'Torfstraße 17',
|
|
||||||
latitude: 52.5416823,
|
|
||||||
longitude: 13.3491223,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysStationToAddress({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: torfstr,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
|
||||||
const atze = {
|
|
||||||
type: 'location',
|
|
||||||
id: '991598902',
|
|
||||||
poi: true,
|
|
||||||
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
|
||||||
latitude: 52.542417,
|
|
||||||
longitude: 13.350437,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, atze, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysStationToPoi({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: atze,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('journeys: via works – with detour', async (t) => {
|
|
||||||
// Going from Westhafen to Wedding via Württembergallee without detour
|
|
||||||
// is currently impossible. We check if the routing engine computes a detour.
|
|
||||||
const res = await client.journeys(westhafen, wedding, {
|
|
||||||
via: württembergallee,
|
|
||||||
results: 1,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysWithDetour({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
detourIds: [württembergallee],
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
|
||||||
// todo: without detour
|
|
||||||
|
|
||||||
|
|
||||||
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
|
||||||
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
|
||||||
await testEarlierLaterJourneys({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
validate,
|
|
||||||
fromId: jungfernheide,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!process.env.VCR_MODE) {
|
|
||||||
tap.test('journeys – leg cycle & alternatives', async (t) => {
|
|
||||||
await testLegCycleAlternatives({
|
|
||||||
test: t,
|
test: t,
|
||||||
fetchJourneys: client.journeys,
|
fetchJourneys: client.journeys,
|
||||||
fromId: blnTiergarten,
|
fromId: blnSchwedterStr,
|
||||||
toId: blnJannowitzbrücke,
|
toId: münchenHbf,
|
||||||
|
when,
|
||||||
|
products: dbProfile.products,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
||||||
|
const torfstr = {
|
||||||
|
type: 'location',
|
||||||
|
address: 'Torfstraße 17',
|
||||||
|
latitude: 52.5416823,
|
||||||
|
longitude: 13.3491223,
|
||||||
|
};
|
||||||
|
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
||||||
|
results: 3,
|
||||||
|
departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysStationToAddress({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
fromId: blnSchwedterStr,
|
||||||
|
to: torfstr,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
||||||
|
const atze = {
|
||||||
|
type: 'location',
|
||||||
|
id: '991598902',
|
||||||
|
poi: true,
|
||||||
|
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
||||||
|
latitude: 52.542417,
|
||||||
|
longitude: 13.350437,
|
||||||
|
};
|
||||||
|
const res = await client.journeys(blnSchwedterStr, atze, {
|
||||||
|
results: 3,
|
||||||
|
departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysStationToPoi({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
fromId: blnSchwedterStr,
|
||||||
|
to: atze,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('journeys: via works – with detour', async (t) => {
|
||||||
|
// Going from Westhafen to Wedding via Württembergallee without detour
|
||||||
|
// is currently impossible. We check if the routing engine computes a detour.
|
||||||
|
const res = await client.journeys(westhafen, wedding, {
|
||||||
|
via: württembergallee,
|
||||||
|
results: 1,
|
||||||
|
departure: when,
|
||||||
|
stopovers: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysWithDetour({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
detourIds: [württembergallee],
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
||||||
|
// todo: without detour
|
||||||
|
|
||||||
|
|
||||||
|
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
||||||
|
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
||||||
|
await testEarlierLaterJourneys({
|
||||||
|
test: t,
|
||||||
|
fetchJourneys: client.journeys,
|
||||||
|
validate,
|
||||||
|
fromId: jungfernheide,
|
||||||
|
toId: münchenHbf,
|
||||||
|
when,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!process.env.VCR_MODE) {
|
||||||
|
tap.test('journeys – leg cycle & alternatives', async (t) => {
|
||||||
|
await testLegCycleAlternatives({
|
||||||
|
test: t,
|
||||||
|
fetchJourneys: client.journeys,
|
||||||
|
fromId: blnTiergarten,
|
||||||
|
toId: blnJannowitzbrücke,
|
||||||
|
when,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tap.test('refreshJourney', async (t) => {
|
||||||
|
const T_MOCK = 1763542800 * 1000;
|
||||||
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
const validate = createValidate({...cfg, when});
|
||||||
|
|
||||||
|
await testRefreshJourney({
|
||||||
|
test: t,
|
||||||
|
fetchJourneys: client.journeys,
|
||||||
|
refreshJourney: client.refreshJourney,
|
||||||
|
validate,
|
||||||
|
fromId: jungfernheide,
|
||||||
|
toId: münchenHbf,
|
||||||
when,
|
when,
|
||||||
});
|
});
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tap.test('refreshJourney', async (t) => {
|
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
const validate = createValidate({...cfg, when});
|
|
||||||
|
|
||||||
await testRefreshJourney({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
refreshJourney: client.refreshJourney,
|
|
||||||
validate,
|
|
||||||
fromId: jungfernheide,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => {
|
tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => {
|
||||||
const blnMehringdamm = '730939';
|
const blnMehringdamm = '730939';
|
||||||
|
|
|
@ -99,184 +99,186 @@ const potsdamHbf = '8012666';
|
||||||
const berlinSüdkreuz = '8011113';
|
const berlinSüdkreuz = '8011113';
|
||||||
const kölnHbf = '8000207';
|
const kölnHbf = '8000207';
|
||||||
|
|
||||||
tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
if (!process.env.VCR_OFF) {
|
||||||
const res = await client.journeys(blnSchwedterStr, münchenHbf, {
|
tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
||||||
results: 4,
|
const res = await client.journeys(blnSchwedterStr, münchenHbf, {
|
||||||
departure: when,
|
results: 4,
|
||||||
stopovers: true,
|
departure: when,
|
||||||
});
|
stopovers: true,
|
||||||
|
});
|
||||||
|
|
||||||
await testJourneysStationToStation({
|
await testJourneysStationToStation({
|
||||||
test: t,
|
test: t,
|
||||||
res,
|
res,
|
||||||
validate,
|
validate,
|
||||||
fromId: blnSchwedterStr,
|
fromId: blnSchwedterStr,
|
||||||
toId: münchenHbf,
|
toId: münchenHbf,
|
||||||
});
|
});
|
||||||
// todo: find a journey where there pricing info is always available
|
// todo: find a journey where there pricing info is always available
|
||||||
for (let journey of res.journeys) {
|
for (let journey of res.journeys) {
|
||||||
if (journey.price) {
|
if (journey.price) {
|
||||||
assertValidPrice(t, journey.price);
|
assertValidPrice(t, journey.price);
|
||||||
|
}
|
||||||
|
if (journey.tickets) {
|
||||||
|
assertValidTickets(t, journey.tickets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (journey.tickets) {
|
t.end();
|
||||||
assertValidTickets(t, journey.tickets);
|
});
|
||||||
|
|
||||||
|
tap.test('refreshJourney – valid tickets', async (t) => {
|
||||||
|
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
||||||
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
|
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
||||||
|
results: 4,
|
||||||
|
departure: when,
|
||||||
|
stopovers: true,
|
||||||
|
});
|
||||||
|
const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
||||||
|
tickets: false,
|
||||||
|
});
|
||||||
|
const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
||||||
|
tickets: true,
|
||||||
|
});
|
||||||
|
for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) {
|
||||||
|
if (res.journey.tickets !== undefined) {
|
||||||
|
assertValidTickets(t, res.journey.tickets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('refreshJourney – valid tickets', async (t) => {
|
t.end();
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
|
|
||||||
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
|
||||||
results: 4,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
|
||||||
tickets: false,
|
|
||||||
});
|
|
||||||
const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
|
||||||
tickets: true,
|
|
||||||
});
|
|
||||||
for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) {
|
|
||||||
if (res.journey.tickets !== undefined) {
|
|
||||||
assertValidTickets(t, res.journey.tickets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: journeys, only one product
|
|
||||||
|
|
||||||
tap.test('journeys – fails with no product', async (t) => {
|
|
||||||
await journeysFailsWithNoProduct({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
products: dbProfile.products,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
|
||||||
const torfstr = {
|
|
||||||
type: 'location',
|
|
||||||
address: 'Torfstraße 17',
|
|
||||||
latitude: 52.5416823,
|
|
||||||
longitude: 13.3491223,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await testJourneysStationToAddress({
|
// todo: journeys, only one product
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: torfstr,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
tap.test('journeys – fails with no product', async (t) => {
|
||||||
const atze = {
|
await journeysFailsWithNoProduct({
|
||||||
type: 'location',
|
|
||||||
id: '991598902',
|
|
||||||
poi: true,
|
|
||||||
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
|
||||||
latitude: 52.542417,
|
|
||||||
longitude: 13.350437,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, atze, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysStationToPoi({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: atze,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('journeys: via works – with detour', async (t) => {
|
|
||||||
// Going from Westhafen to Wedding via Württembergallee without detour
|
|
||||||
// is currently impossible. We check if the routing engine computes a detour.
|
|
||||||
const res = await client.journeys(westhafen, wedding, {
|
|
||||||
via: württembergallee,
|
|
||||||
results: 1,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysWithDetour({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
detourIds: [württembergallee],
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
|
||||||
// todo: without detour
|
|
||||||
|
|
||||||
|
|
||||||
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
|
||||||
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
|
||||||
await testEarlierLaterJourneys({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
validate,
|
|
||||||
fromId: jungfernheide,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!process.env.VCR_MODE) {
|
|
||||||
tap.test('journeys – leg cycle & alternatives', async (t) => {
|
|
||||||
await testLegCycleAlternatives({
|
|
||||||
test: t,
|
test: t,
|
||||||
fetchJourneys: client.journeys,
|
fetchJourneys: client.journeys,
|
||||||
fromId: blnTiergarten,
|
fromId: blnSchwedterStr,
|
||||||
toId: blnJannowitzbrücke,
|
toId: münchenHbf,
|
||||||
|
when,
|
||||||
|
products: dbProfile.products,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
||||||
|
const torfstr = {
|
||||||
|
type: 'location',
|
||||||
|
address: 'Torfstraße 17',
|
||||||
|
latitude: 52.5416823,
|
||||||
|
longitude: 13.3491223,
|
||||||
|
};
|
||||||
|
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
||||||
|
results: 3,
|
||||||
|
departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysStationToAddress({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
fromId: blnSchwedterStr,
|
||||||
|
to: torfstr,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
||||||
|
const atze = {
|
||||||
|
type: 'location',
|
||||||
|
id: '991598902',
|
||||||
|
poi: true,
|
||||||
|
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
||||||
|
latitude: 52.542417,
|
||||||
|
longitude: 13.350437,
|
||||||
|
};
|
||||||
|
const res = await client.journeys(blnSchwedterStr, atze, {
|
||||||
|
results: 3,
|
||||||
|
departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysStationToPoi({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
fromId: blnSchwedterStr,
|
||||||
|
to: atze,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('journeys: via works – with detour', async (t) => {
|
||||||
|
// Going from Westhafen to Wedding via Württembergallee without detour
|
||||||
|
// is currently impossible. We check if the routing engine computes a detour.
|
||||||
|
const res = await client.journeys(westhafen, wedding, {
|
||||||
|
via: württembergallee,
|
||||||
|
results: 1,
|
||||||
|
departure: when,
|
||||||
|
stopovers: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testJourneysWithDetour({
|
||||||
|
test: t,
|
||||||
|
res,
|
||||||
|
validate,
|
||||||
|
detourIds: [württembergallee],
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
||||||
|
// todo: without detour
|
||||||
|
|
||||||
|
|
||||||
|
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
||||||
|
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
||||||
|
await testEarlierLaterJourneys({
|
||||||
|
test: t,
|
||||||
|
fetchJourneys: client.journeys,
|
||||||
|
validate,
|
||||||
|
fromId: jungfernheide,
|
||||||
|
toId: münchenHbf,
|
||||||
|
when,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!process.env.VCR_MODE) {
|
||||||
|
tap.test('journeys – leg cycle & alternatives', async (t) => {
|
||||||
|
await testLegCycleAlternatives({
|
||||||
|
test: t,
|
||||||
|
fetchJourneys: client.journeys,
|
||||||
|
fromId: blnTiergarten,
|
||||||
|
toId: blnJannowitzbrücke,
|
||||||
|
when,
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tap.test('refreshJourney', async (t) => {
|
||||||
|
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
||||||
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
const validate = createValidate({...cfg, when});
|
||||||
|
|
||||||
|
await testRefreshJourney({
|
||||||
|
test: t,
|
||||||
|
fetchJourneys: client.journeys,
|
||||||
|
refreshJourney: client.refreshJourney,
|
||||||
|
validate,
|
||||||
|
fromId: jungfernheide,
|
||||||
|
toId: münchenHbf,
|
||||||
when,
|
when,
|
||||||
});
|
});
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
tap.test('refreshJourney', async (t) => {
|
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
const validate = createValidate({...cfg, when});
|
|
||||||
|
|
||||||
await testRefreshJourney({
|
/*
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
refreshJourney: client.refreshJourney,
|
|
||||||
validate,
|
|
||||||
fromId: jungfernheide,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => {
|
tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => {
|
||||||
const blnMehringdamm = '730939';
|
const blnMehringdamm = '730939';
|
||||||
const blnStadtmitte = '732541';
|
const blnStadtmitte = '732541';
|
||||||
|
@ -366,33 +368,34 @@ tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to S
|
||||||
}
|
}
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
tap.test('trip details', async (t) => {
|
tap.test('trip details', async (t) => {
|
||||||
const res = await client.journeys(berlinHbf, münchenHbf, {
|
const res = await client.journeys(berlinHbf, münchenHbf, {
|
||||||
results: 1, departure: when,
|
results: 1, departure: when,
|
||||||
|
});
|
||||||
|
|
||||||
|
const p = res.journeys[0].legs.find(l => !l.walking);
|
||||||
|
t.ok(p.tripId, 'precondition failed');
|
||||||
|
t.ok(p.line.name, 'precondition failed');
|
||||||
|
|
||||||
|
const tripRes = await client.trip(p.tripId, {when});
|
||||||
|
|
||||||
|
const validate = createValidate(cfg, {
|
||||||
|
trip: (cfg) => {
|
||||||
|
const validateTrip = createValidateTrip(cfg);
|
||||||
|
const validateTripWithFakeDirection = (val, trip, name) => {
|
||||||
|
validateTrip(val, {
|
||||||
|
...trip,
|
||||||
|
direction: trip.direction || 'foo', // todo, see #49
|
||||||
|
}, name);
|
||||||
|
};
|
||||||
|
return validateTripWithFakeDirection;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
validate(t, tripRes, 'tripResult', 'tripRes');
|
||||||
|
|
||||||
|
t.end();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
const p = res.journeys[0].legs.find(l => !l.walking);
|
|
||||||
t.ok(p.tripId, 'precondition failed');
|
|
||||||
t.ok(p.line.name, 'precondition failed');
|
|
||||||
|
|
||||||
const tripRes = await client.trip(p.tripId, {when});
|
|
||||||
|
|
||||||
const validate = createValidate(cfg, {
|
|
||||||
trip: (cfg) => {
|
|
||||||
const validateTrip = createValidateTrip(cfg);
|
|
||||||
const validateTripWithFakeDirection = (val, trip, name) => {
|
|
||||||
validateTrip(val, {
|
|
||||||
...trip,
|
|
||||||
direction: trip.direction || 'foo', // todo, see #49
|
|
||||||
}, name);
|
|
||||||
};
|
|
||||||
return validateTripWithFakeDirection;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
validate(t, tripRes, 'tripResult', 'tripRes');
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||||
const res = await client.departures(blnSchwedterStr, {
|
const res = await client.departures(blnSchwedterStr, {
|
||||||
|
|
File diff suppressed because one or more lines are too long
492
test/fixtures/dbbahnhof-departures.js
vendored
Normal file
492
test/fixtures/dbbahnhof-departures.js
vendored
Normal file
|
@ -0,0 +1,492 @@
|
||||||
|
const dbDepartures = [
|
||||||
|
{
|
||||||
|
tripId: '20250322-f6cd6d71-510f-378c-b825-fbb9380f5b03',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000105',
|
||||||
|
name: 'Frankfurt(Main)Hbf',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:42:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:42:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: '11',
|
||||||
|
plannedPlatform: '11',
|
||||||
|
direction: 'Fulda',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 'rb51',
|
||||||
|
fahrtNr: '51',
|
||||||
|
name: 'RB51',
|
||||||
|
public: true,
|
||||||
|
adminCode: '8005KG',
|
||||||
|
productName: 'RB',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'regional',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'bicycle-transport',
|
||||||
|
summary: 'Limited bicycle transport possible',
|
||||||
|
text: 'Limited bicycle transport possible',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000115',
|
||||||
|
name: 'Fulda',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250321-69f912f7-2b47-3ffd-b00f-708e8c568f6b',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:43:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:42:00+01:00',
|
||||||
|
delay: 60,
|
||||||
|
platform: '103',
|
||||||
|
plannedPlatform: '103',
|
||||||
|
direction: 'Wiesbaden Hbf',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's1',
|
||||||
|
fahrtNr: '1',
|
||||||
|
name: 'S1',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'bicycle-transport',
|
||||||
|
summary: 'Limited bicycle transport possible',
|
||||||
|
text: 'Limited bicycle transport possible',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000250',
|
||||||
|
name: 'Wiesbaden Hbf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-fcf0de07-fc6e-3aeb-9757-3b08e6760c3f',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:44:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:44:00+01:00',
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: null,
|
||||||
|
direction: 'Bad Soden(Taunus)',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 'sev',
|
||||||
|
fahrtNr: '',
|
||||||
|
name: 'SEV',
|
||||||
|
public: true,
|
||||||
|
adminCode: 'B4',
|
||||||
|
productName: 'SEV',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'regional',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'replacement-service',
|
||||||
|
summary: 'Replacement bus for {{1}}',
|
||||||
|
text: 'Replacement bus for {{1}}',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000752',
|
||||||
|
name: 'Bad Soden(Taunus)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250321-bff43415-303b-36ad-ae4e-8d9605fc3f1e',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:44:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '102',
|
||||||
|
prognosedPlatform: '102',
|
||||||
|
direction: 'Offenbach(Main)Ost',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's8',
|
||||||
|
fahrtNr: '8',
|
||||||
|
name: 'S8',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'unplanned-info',
|
||||||
|
summary: 'Defective signal box',
|
||||||
|
text: 'Defective signal box',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'canceled-trip',
|
||||||
|
summary: 'Trip cancelled',
|
||||||
|
text: 'Trip cancelled',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8004645',
|
||||||
|
name: 'Offenbach(Main)Ost',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-1423f343-0076-3f29-b1e8-004dc1f27068',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:47:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:47:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: '101',
|
||||||
|
plannedPlatform: '101',
|
||||||
|
direction: 'Darmstadt Hbf',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's6',
|
||||||
|
fahrtNr: '6',
|
||||||
|
name: 'S6',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'bicycle-transport',
|
||||||
|
summary: 'Limited bicycle transport possible',
|
||||||
|
text: 'Limited bicycle transport possible',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000068',
|
||||||
|
name: 'Darmstadt Hbf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-f2c61208-75b1-3c19-a580-172988addf2c',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:47:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '103',
|
||||||
|
prognosedPlatform: '103',
|
||||||
|
direction: 'Wiesbaden Hbf',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's8',
|
||||||
|
fahrtNr: '8',
|
||||||
|
name: 'S8',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'unplanned-info',
|
||||||
|
summary: 'Defective signal box',
|
||||||
|
text: 'Defective signal box',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'canceled-trip',
|
||||||
|
summary: 'Trip cancelled',
|
||||||
|
text: 'Trip cancelled',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000250',
|
||||||
|
name: 'Wiesbaden Hbf',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-841901b7-7420-3890-8691-05c8cbbacdce',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T01:05:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:49:00+01:00',
|
||||||
|
delay: 960,
|
||||||
|
platform: '102',
|
||||||
|
plannedPlatform: '102',
|
||||||
|
direction: 'Rödermark-Ober Roden',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's1',
|
||||||
|
fahrtNr: '1',
|
||||||
|
name: 'S1',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'unplanned-info',
|
||||||
|
summary: 'Repair on the turnout',
|
||||||
|
text: 'Repair on the turnout',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'bicycle-transport',
|
||||||
|
summary: 'Limited bicycle transport possible',
|
||||||
|
text: 'Limited bicycle transport possible',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000285',
|
||||||
|
name: 'Rödermark-Ober Roden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-08673537-4773-3e90-afa3-ab467119a609',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '100010',
|
||||||
|
name: 'Hauptbahnhof, Frankfurt a.M.',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:50:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:50:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: null,
|
||||||
|
direction: 'Bockenheimer Warte, Frankfurt a.M.',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 'u4',
|
||||||
|
fahrtNr: '4',
|
||||||
|
name: 'U4',
|
||||||
|
public: true,
|
||||||
|
adminCode: 'rmv255',
|
||||||
|
productName: 'U',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'subway',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '101201',
|
||||||
|
name: 'Bockenheimer Warte, Frankfurt a.M.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-29a07ddd-969e-3660-ae2e-4d6c6af4a866',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000105',
|
||||||
|
name: 'Frankfurt(Main)Hbf',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:50:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '2',
|
||||||
|
prognosedPlatform: '2',
|
||||||
|
direction: 'Riedstadt-Goddelau',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's7',
|
||||||
|
fahrtNr: '7',
|
||||||
|
name: 'S7',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'unplanned-info',
|
||||||
|
summary: 'Short-term unavailability of employees',
|
||||||
|
text: 'Short-term unavailability of employees',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'canceled-trip',
|
||||||
|
summary: 'Trip cancelled',
|
||||||
|
text: 'Trip cancelled',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000126',
|
||||||
|
name: 'Riedstadt-Goddelau',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-01575b72-00ad-36da-bc88-49410d87321a',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:52:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:52:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: '103',
|
||||||
|
plannedPlatform: '103',
|
||||||
|
direction: 'Niedernhausen(Taunus)',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's2',
|
||||||
|
fahrtNr: '2',
|
||||||
|
name: 'S2',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'bicycle-transport',
|
||||||
|
summary: 'Limited bicycle transport possible',
|
||||||
|
text: 'Limited bicycle transport possible',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8004400',
|
||||||
|
name: 'Niedernhausen(Taunus)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-2b5b5913-2b2d-39da-b29d-2fff067ba6a2',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:52:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '101',
|
||||||
|
prognosedPlatform: '101',
|
||||||
|
direction: 'Frankfurt(Main)Süd',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's4',
|
||||||
|
fahrtNr: '4',
|
||||||
|
name: 'S4',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: null,
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'unplanned-info',
|
||||||
|
summary: 'Defective signal box',
|
||||||
|
text: 'Defective signal box',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'canceled-trip',
|
||||||
|
summary: 'Trip cancelled',
|
||||||
|
text: 'Trip cancelled',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8002041',
|
||||||
|
name: 'Frankfurt(Main)Süd',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export {
|
||||||
|
dbDepartures,
|
||||||
|
};
|
1
test/fixtures/dbbahnhof-departures.json
vendored
Normal file
1
test/fixtures/dbbahnhof-departures.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/dbnav-refresh-journey.js
vendored
1
test/fixtures/dbnav-refresh-journey.js
vendored
|
@ -212,6 +212,7 @@ const dbNavJourney = {
|
||||||
amount: 43.99,
|
amount: 43.99,
|
||||||
currency: 'EUR',
|
currency: 'EUR',
|
||||||
hint: null,
|
hint: null,
|
||||||
|
partialFare: false,
|
||||||
},
|
},
|
||||||
tickets: [
|
tickets: [
|
||||||
{
|
{
|
||||||
|
|
580
test/fixtures/dbris-departures.js
vendored
Normal file
580
test/fixtures/dbris-departures.js
vendored
Normal file
|
@ -0,0 +1,580 @@
|
||||||
|
const dbDepartures = [
|
||||||
|
{
|
||||||
|
tripId: '20250321-69f912f7-2b47-3ffd-b00f-708e8c568f6b',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:43:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:42:00+01:00',
|
||||||
|
delay: 60,
|
||||||
|
platform: '103',
|
||||||
|
plannedPlatform: '103',
|
||||||
|
direction: 'Wiesbaden Hbf',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-1',
|
||||||
|
fahrtNr: '35182',
|
||||||
|
name: 'S 1',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: '43',
|
||||||
|
summary: 'Verspätung eines vorausfahrenden Zuges',
|
||||||
|
text: 'Verspätung eines vorausfahrenden Zuges',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000250',
|
||||||
|
name: 'Wiesbaden Hbf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-fcf0de07-fc6e-3aeb-9757-3b08e6760c3f',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:44:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:44:00+01:00',
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: null,
|
||||||
|
direction: null,
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 'bus-sev-353821',
|
||||||
|
fahrtNr: '353821',
|
||||||
|
name: 'Bus SEV',
|
||||||
|
public: true,
|
||||||
|
adminCode: 'B4',
|
||||||
|
productName: 'Bus',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'regional',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: '---',
|
||||||
|
name: 'Busse/SEV S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000752',
|
||||||
|
name: 'Bad Soden(Taunus)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250321-bff43415-303b-36ad-ae4e-8d9605fc3f1e',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:44:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '102',
|
||||||
|
prognosedPlatform: '102',
|
||||||
|
direction: 'Offenbach(Main)Ost',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-8',
|
||||||
|
fahrtNr: '35883',
|
||||||
|
name: 'S 8',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
summary: 'S8: Kein Betrieb möglich. Grund: Erkrankung von Stellwerkspersonal. Dauer der Beeinträchtigung bis Betriebsschluss. Ersatzverkehr mit Bussen eingerichtet. Andere Verkehrsmittel mit einbeziehen. Reiseverbindung frühzeitig prüfen.',
|
||||||
|
text: 'Auf der S8 ist kein Betrieb möglich. Der Grund ist eine Erkrankung von Stellwerkspersonal. Dauer der Beeinträchtigung bis Betriebsschluss. Wir haben für Sie einen Ersatzverkehr mit 8 Bussen der Firma Holiday-Reisen GmbH zwischen Wiesbaden Hbf und Frankfurt(M) Flughafen Regionalbf eingerichtet. Außerdem haben wir für Sie ab ca. 01:00 Uhr einen Ersatzverkehr mit 4 Bussen der Firma Holiday-Reisen GmbH zwischen Hanau Hbf und Offenbach(Main)Ost eingerichtet. Beziehen Sie auch andere Verkehrsmittel mit ein, um an Ihr Reiseziel zu gelangen. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können.',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '40',
|
||||||
|
summary: 'defektes Stellwerk',
|
||||||
|
text: 'defektes Stellwerk',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'CUSTOMER_TEXT',
|
||||||
|
summary: 'Auf der S8 und S9 kommt es zwischen Wiesbaden Hbf - Frankfurt(M) Flughafen Regionalbf - Frankfurt(Main)Hbf - Offenbach(Main)Ost in den Nächten bis zum 22.03.25 zu geänderten Fahrtzeiten, einzelnen Umleitungen, Teil- und Zugausfällen. Der Grund sind Bauarbeiten. Auf dem jeweils ausfallenden Abschnitt haben wir für Sie einen Ersatzverkehr mit Bussen eingerichtet. Die Mitnahme von Fahrrädern im Bus ist ausgeschlossen. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können. Die Fahrplanänderungen zu dieser Baumaßnahme sind in die Online-Fahrplanauskünfte eingearbeitet.',
|
||||||
|
text: 'Auf der S8 und S9 kommt es zwischen Wiesbaden Hbf - Frankfurt(M) Flughafen Regionalbf - Frankfurt(Main)Hbf - Offenbach(Main)Ost in den Nächten bis zum 22.03.25 zu geänderten Fahrtzeiten, einzelnen Umleitungen, Teil- und Zugausfällen. Der Grund sind Bauarbeiten. Auf dem jeweils ausfallenden Abschnitt haben wir für Sie einen Ersatzverkehr mit Bussen eingerichtet. Die Mitnahme von Fahrrädern im Bus ist ausgeschlossen. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können. Die Fahrplanänderungen zu dieser Baumaßnahme sind in die Online-Fahrplanauskünfte eingearbeitet.',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8004645',
|
||||||
|
name: 'Offenbach(Main)Ost',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-f2c61208-75b1-3c19-a580-172988addf2c',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:47:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '103',
|
||||||
|
prognosedPlatform: '103',
|
||||||
|
direction: 'Wiesbaden Hbf',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-8',
|
||||||
|
fahrtNr: '35884',
|
||||||
|
name: 'S 8',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
summary: 'S8: Kein Betrieb möglich. Grund: Erkrankung von Stellwerkspersonal. Dauer der Beeinträchtigung bis Betriebsschluss. Ersatzverkehr mit Bussen eingerichtet. Andere Verkehrsmittel mit einbeziehen. Reiseverbindung frühzeitig prüfen.',
|
||||||
|
text: 'Auf der S8 ist kein Betrieb möglich. Der Grund ist eine Erkrankung von Stellwerkspersonal. Dauer der Beeinträchtigung bis Betriebsschluss. Wir haben für Sie einen Ersatzverkehr mit 8 Bussen der Firma Holiday-Reisen GmbH zwischen Wiesbaden Hbf und Frankfurt(M) Flughafen Regionalbf eingerichtet. Außerdem haben wir für Sie ab ca. 01:00 Uhr einen Ersatzverkehr mit 4 Bussen der Firma Holiday-Reisen GmbH zwischen Hanau Hbf und Offenbach(Main)Ost eingerichtet. Beziehen Sie auch andere Verkehrsmittel mit ein, um an Ihr Reiseziel zu gelangen. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können.',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '40',
|
||||||
|
summary: 'defektes Stellwerk',
|
||||||
|
text: 'defektes Stellwerk',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'CUSTOMER_TEXT',
|
||||||
|
summary: 'Die Halte Frankfurt(Main)-Gateway Gardens, Frankfurt(M) Flughafen Regionalbf und Kelsterbach entfallen. Bitte prüfen Sie Ihre Reiseverbindung kurz vor der Abfahrt des Zuges.',
|
||||||
|
text: 'Die Halte Frankfurt(Main)-Gateway Gardens, Frankfurt(M) Flughafen Regionalbf und Kelsterbach entfallen. Bitte prüfen Sie Ihre Reiseverbindung kurz vor der Abfahrt des Zuges.',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'CUSTOMER_TEXT',
|
||||||
|
summary: 'Auf der S8 und S9 kommt es zwischen Wiesbaden Hbf - Frankfurt(M) Flughafen Regionalbf - Frankfurt(Main)Hbf - Offenbach(Main)Ost in den Nächten bis zum 22.03.25 zu geänderten Fahrtzeiten, einzelnen Umleitungen, Teil- und Zugausfällen. Der Grund sind Bauarbeiten. Auf dem jeweils ausfallenden Abschnitt haben wir für Sie einen Ersatzverkehr mit Bussen eingerichtet. Die Mitnahme von Fahrrädern im Bus ist ausgeschlossen. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können. Die Fahrplanänderungen zu dieser Baumaßnahme sind in die Online-Fahrplanauskünfte eingearbeitet.',
|
||||||
|
text: 'Auf der S8 und S9 kommt es zwischen Wiesbaden Hbf - Frankfurt(M) Flughafen Regionalbf - Frankfurt(Main)Hbf - Offenbach(Main)Ost in den Nächten bis zum 22.03.25 zu geänderten Fahrtzeiten, einzelnen Umleitungen, Teil- und Zugausfällen. Der Grund sind Bauarbeiten. Auf dem jeweils ausfallenden Abschnitt haben wir für Sie einen Ersatzverkehr mit Bussen eingerichtet. Die Mitnahme von Fahrrädern im Bus ist ausgeschlossen. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können. Die Fahrplanänderungen zu dieser Baumaßnahme sind in die Online-Fahrplanauskünfte eingearbeitet.',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000250',
|
||||||
|
name: 'Wiesbaden Hbf',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-1423f343-0076-3f29-b1e8-004dc1f27068',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:47:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:47:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: '101',
|
||||||
|
plannedPlatform: '101',
|
||||||
|
direction: 'Darmstadt Hbf',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-6',
|
||||||
|
fahrtNr: '36683',
|
||||||
|
name: 'S 6',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000068',
|
||||||
|
name: 'Darmstadt Hbf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-841901b7-7420-3890-8691-05c8cbbacdce',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T01:05:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:49:00+01:00',
|
||||||
|
delay: 960,
|
||||||
|
platform: '102',
|
||||||
|
plannedPlatform: '102',
|
||||||
|
direction: 'Rödermark-Ober Roden',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-1',
|
||||||
|
fahrtNr: '35185',
|
||||||
|
name: 'S 1',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: '64',
|
||||||
|
summary: 'Reparatur an einer Weiche',
|
||||||
|
text: 'Reparatur an einer Weiche',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000285',
|
||||||
|
name: 'Rödermark-Ober Roden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-08673537-4773-3e90-afa3-ab467119a609',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '100010',
|
||||||
|
name: 'Hauptbahnhof, Frankfurt a.M.',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:50:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:50:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: null,
|
||||||
|
direction: 'Bockenheimer Warte, Frankfurt a.M.',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 'u-4',
|
||||||
|
fahrtNr: '1954',
|
||||||
|
name: 'U 4',
|
||||||
|
public: true,
|
||||||
|
adminCode: 'rmv255',
|
||||||
|
productName: 'U',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'subway',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DPN',
|
||||||
|
name: 'Nahreisezug',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '101201',
|
||||||
|
name: 'Bockenheimer Warte, Frankfurt a.M.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-29a07ddd-969e-3660-ae2e-4d6c6af4a866',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000105',
|
||||||
|
name: 'Frankfurt(Main)Hbf',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:50:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '2',
|
||||||
|
prognosedPlatform: '2',
|
||||||
|
direction: 'Riedstadt-Goddelau',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-7',
|
||||||
|
fahrtNr: '35787',
|
||||||
|
name: 'S 7',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
summary: 'S7: Kein Betrieb möglich. Grund: Kurzfristige Erkrankung von Personal. Am 21.03.25/22.03.25 von 18:00 Uhr bis voraussichtlich 03:00 Uhr. Alternative Reisemöglichkeit: RE70. Die Züge halten zusätzlich in Riedstadt-Wolfskehlen - Groß Gerau-Dornheim - Zeppelinheim. Reiseverbindung frühzeitig prüfen.',
|
||||||
|
text: 'Auf der S7 ist kein Betrieb möglich. Der Grund ist eine kurzfristige Erkrankung von Personal. Die Beeinträchtigung ist am 21.03.25/22.03.25 von 18:00 Uhr bis voraussichtlich 03:00 Uhr. Alternative Reisemöglichkeit: RE70. Die Züge halten zusätzlich für Sie in Riedstadt-Wolfskehlen - Groß Gerau-Dornheim - Zeppelinheim zum Ein- und Ausstieg. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können.',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '49',
|
||||||
|
summary: 'kurzfristiger Personalausfall',
|
||||||
|
text: 'kurzfristiger Personalausfall',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8000126',
|
||||||
|
name: 'Riedstadt-Goddelau',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-01575b72-00ad-36da-bc88-49410d87321a',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: '2025-03-22T00:52:00+01:00',
|
||||||
|
plannedWhen: '2025-03-22T00:52:00+01:00',
|
||||||
|
delay: 0,
|
||||||
|
platform: '103',
|
||||||
|
plannedPlatform: '103',
|
||||||
|
direction: 'Niedernhausen(Taunus)',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-2',
|
||||||
|
fahrtNr: '35284',
|
||||||
|
name: 'S 2',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8004400',
|
||||||
|
name: 'Niedernhausen(Taunus)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tripId: '20250322-2b5b5913-2b2d-39da-b29d-2fff067ba6a2',
|
||||||
|
stop: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8098105',
|
||||||
|
name: 'Frankfurt Hbf (tief)',
|
||||||
|
},
|
||||||
|
when: null,
|
||||||
|
plannedWhen: '2025-03-22T00:52:00+01:00',
|
||||||
|
prognosedWhen: null,
|
||||||
|
delay: null,
|
||||||
|
platform: null,
|
||||||
|
plannedPlatform: '101',
|
||||||
|
prognosedPlatform: '101',
|
||||||
|
direction: 'Frankfurt(Main)Süd',
|
||||||
|
provenance: null,
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
id: 's-4',
|
||||||
|
fahrtNr: '35483',
|
||||||
|
name: 'S 4',
|
||||||
|
public: true,
|
||||||
|
adminCode: '800528',
|
||||||
|
productName: 'S',
|
||||||
|
mode: 'train',
|
||||||
|
product: 'suburban',
|
||||||
|
operator: {
|
||||||
|
type: 'operator',
|
||||||
|
id: 'DB',
|
||||||
|
name: 'DB Regio, S-Bahn Rhein-Main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remarks: [
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
summary: 'S3 und S4: Kein Betrieb möglich. Am 21./22.03.25 von 20:00 Uhr bis voraussichtlich 04:00 Uhr. Grund: Erkrankung von Stellwerkspersonal. Ersatzverkehr mit Bussen eingerichtet. Reiseverbindung frühzeitig prüfen.',
|
||||||
|
text: 'Auf der S3 und S4 ist kein Betrieb möglich. Die Beeinträchtigung ist am 21./22.03.25 von 20:00 Uhr bis voraussichtlich 04:00 Uhr. Der Grund ist eine Erkrankung von Stellwerkspersonal. Wir haben für Sie einen Ersatzverkehr mit 8 Bussen der Firma Holiday-Reisen GmbH zwischen Frankfurt(Main)Hbf und Bad Soden(Taunus) eingerichtet. Außerdem haben wir für Sie einen Ersatzverkehr mit 7 Bussen der Firma Holiday-Reisen GmbH zwischen Frankfurt(Main)Hbf und Kronberg(Taunus) eingerichtet. Bitte informieren Sie sich frühzeitig über Ihre geplanten Verbindungen und wählen Sie gegebenenfalls eine frühere Zugverbindung, um Ihre Anschlüsse an Ihren Umsteigebahnhöfen erreichen zu können.',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '40',
|
||||||
|
summary: 'defektes Stellwerk',
|
||||||
|
text: 'defektes Stellwerk',
|
||||||
|
type: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'FK',
|
||||||
|
summary: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
text: 'Fahrradmitnahme begrenzt möglich',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'EH',
|
||||||
|
summary: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
text: 'Fahrzeuggebundene Einstiegshilfe vorhanden',
|
||||||
|
type: 'hint',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin: null,
|
||||||
|
destination: {
|
||||||
|
type: 'station',
|
||||||
|
id: '8002041',
|
||||||
|
name: 'Frankfurt(Main)Süd',
|
||||||
|
},
|
||||||
|
cancelled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export {
|
||||||
|
dbDepartures,
|
||||||
|
};
|
1
test/fixtures/dbris-departures.json
vendored
Normal file
1
test/fixtures/dbris-departures.json
vendored
Normal file
File diff suppressed because one or more lines are too long
2
test/fixtures/dbweb-departures.js
vendored
2
test/fixtures/dbweb-departures.js
vendored
|
@ -1290,7 +1290,7 @@ const dbwebDepartures = [
|
||||||
},
|
},
|
||||||
remarks: [
|
remarks: [
|
||||||
{
|
{
|
||||||
code: undefined,
|
code: 'HALT_AUSFALL',
|
||||||
summary: 'Halt entfällt',
|
summary: 'Halt entfällt',
|
||||||
text: 'Halt entfällt',
|
text: 'Halt entfällt',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
2
test/fixtures/dbweb-journey.js
vendored
2
test/fixtures/dbweb-journey.js
vendored
|
@ -292,7 +292,7 @@ const dbwebJourney = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
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$$$$$$',
|
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$$$$$$',
|
||||||
price: {amount: 31.49, currency: 'EUR', hint: null},
|
price: {amount: 31.49, currency: 'EUR', hint: null, partialFare: false},
|
||||||
tickets: [{
|
tickets: [{
|
||||||
name: 'from',
|
name: 'from',
|
||||||
priceObj: {
|
priceObj: {
|
||||||
|
|
1
test/fixtures/dbweb-refresh-journey.js
vendored
1
test/fixtures/dbweb-refresh-journey.js
vendored
|
@ -210,6 +210,7 @@ const dbJourney = {
|
||||||
amount: 27.99,
|
amount: 27.99,
|
||||||
currency: 'EUR',
|
currency: 'EUR',
|
||||||
hint: null,
|
hint: null,
|
||||||
|
partialFare: false,
|
||||||
},
|
},
|
||||||
tickets: [
|
tickets: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,6 +41,10 @@ const berlinWienQuery0 = Object.freeze(
|
||||||
einstiegsTypList: [
|
einstiegsTypList: [
|
||||||
'STANDARD',
|
'STANDARD',
|
||||||
],
|
],
|
||||||
|
fahrverguenstigungen: {
|
||||||
|
deutschlandTicketVorhanden: undefined,
|
||||||
|
nurDeutschlandTicketVerbindungen: undefined,
|
||||||
|
},
|
||||||
klasse: 'KLASSE_2',
|
klasse: 'KLASSE_2',
|
||||||
reiseHin: {
|
reiseHin: {
|
||||||
wunsch: {
|
wunsch: {
|
||||||
|
|
|
@ -57,8 +57,8 @@ const berlinWienQuery0 = Object.freeze(
|
||||||
sitzplatzOnly: false,
|
sitzplatzOnly: false,
|
||||||
bikeCarriage: false,
|
bikeCarriage: false,
|
||||||
reservierungsKontingenteVorhanden: false,
|
reservierungsKontingenteVorhanden: false,
|
||||||
nurDeutschlandTicketVerbindungen: false,
|
nurDeutschlandTicketVerbindungen: undefined,
|
||||||
deutschlandTicketVorhanden: false,
|
deutschlandTicketVorhanden: undefined,
|
||||||
maxUmstiege: null,
|
maxUmstiege: null,
|
||||||
zwischenhalte: null,
|
zwischenhalte: null,
|
||||||
minUmstiegszeit: 0,
|
minUmstiegszeit: 0,
|
||||||
|
|
Loading…
Add table
Reference in a new issue