Compare commits

..

No commits in common. "6c2081c14eb622ed7f04fa127f9dc3c5ff76b59a" and "14b80dbf33d5a47ae0ca6527ea2004ff82239fd3" have entirely different histories.

17 changed files with 208 additions and 279 deletions

View file

@ -621,11 +621,7 @@
"Zusammenfassung",
"Züttlingen",
"Zwickauer",
"zwischenhalte",
"bestprice",
"intervalle",
"Intervalle",
"tagesbest"
"zwischenhalte"
],
"ignorePaths": [
"docs/dumps/**",

View file

@ -75,9 +75,7 @@ With `opt`, you can override the default options, which look like this:
subStops: true, // not supported
entrances: true, // not supported
remarks: true, // parse & expose hints & warnings?
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
scheduledDays: false, // not yet supported
firstClass: false, // first or second class for tickets
loyaltyCard: null, // BahnCards etc., see below
language: 'en', // language to get results in

View file

@ -365,19 +365,7 @@ paths:
default: true
- name: scheduledDays
in: query
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
description: Parse & return dates each journey is valid on?
schema:
type: boolean
default: false
@ -695,19 +683,7 @@ paths:
default: true
- name: scheduledDays
in: query
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
description: Parse & return dates the journey is valid on?
schema:
type: boolean
default: false

View file

@ -85,10 +85,10 @@ const createClient = (profile, userAgent, opt = {}) => {
throw new TypeError('type must be a non-empty string.');
}
if (!profile.departuresGetPasslist && opt.stopovers) {
if (!profile.departuresGetPasslist && 'stopovers' in opt) {
throw new Error('opt.stopovers is not supported by this endpoint');
}
if (!profile.departuresStbFltrEquiv && 'includeRelatedStations' in opt) {
if (!profile.departuresStbFltrEquiv && 'includeRelatedStations' in opt) { // TODO artificially filter?
throw new Error('opt.includeRelatedStations is not supported by this endpoint');
}
@ -185,8 +185,6 @@ const createClient = (profile, userAgent, opt = {}) => {
entrances: true, // parse & expose entrances of stops/stations?
remarks: true, // parse & expose hints & warnings?
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
}, opt);
if (opt.when !== undefined) {
@ -212,15 +210,9 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatJourneysReq({profile, opt}, from, to, when, outFrwd, journeysRef);
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
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 verbindungen = Number.isInteger(opt.results) ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
const journeys = verbindungen
.map(j => profile.parseJourney(ctx, j));
if (opt.bestprice) {
journeys.sort((a, b) => a.price?.amount - b.price?.amount);
}
return {
earlierRef: res.verbindungReference?.earlier || res.frueherContext || null,

View file

@ -50,7 +50,7 @@ const mapRouteParsers = (route, parsers) => {
firstClass: {
description: 'Search for first-class options?',
type: 'boolean',
default: false,
default: 'false',
parse: parseBoolean,
},
loyaltyCard: {
@ -66,18 +66,6 @@ const mapRouteParsers = (route, parsers) => {
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,
},
};
};

View file

@ -1,6 +1,5 @@
{
"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",
"refreshJourneysEndpointPolyline": "https://app.vendo.noncd.db.de/mob/trip/recon",
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",

View file

@ -55,11 +55,8 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
if (journeysRef) {
query.reiseHin.wunsch.context = journeysRef;
}
if (opt.notOnlyFastRoutes) {
query.reiseHin.wunsch.economic = true;
}
return {
endpoint: opt.bestprice ? profile.bestpriceEndpoint : profile.journeysEndpoint,
endpoint: ctx.profile.journeysEndpoint,
body: query,
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v8+json'),
method: 'post',

View file

@ -1,6 +1,5 @@
{
"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",
"refreshJourneysEndpointPolyline": "https://int.bahn.de/web/api/reiseloesung/verbindung",
"locationsEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte",

View file

@ -13,7 +13,7 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
deutschlandTicketVorhanden: false,
nurDeutschlandTicketVerbindungen: false,
reservierungsKontingenteVorhanden: false,
schnelleVerbindungen: !opt.notOnlyFastRoutes,
schnelleVerbindungen: true,
sitzplatzOnly: false,
abfahrtsHalt: from.lid,
zwischenhalte: opt.via
@ -35,14 +35,14 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
if (opt.results !== null) {
// TODO query.numF = opt.results;
}
query = Object.assign(query, profile.formatTravellers(ctx));
query = Object.assign(query, ctx.profile.formatTravellers(ctx));
return {
endpoint: opt.bestprice ? profile.bestpriceEndpoint : profile.journeysEndpoint,
endpoint: ctx.profile.journeysEndpoint,
body: query,
method: 'post',
};
};
// TODO poly conditional other endpoint
const formatRefreshJourneyReq = (ctx, refreshToken) => {
const {profile, opt} = ctx;
if (opt.tickets) {

View file

@ -69,15 +69,7 @@ const parseJourney = (ctx, jj) => { // j = raw journey
// TODO
if (opt.scheduledDays && j.serviceDays) {
// todo [breaking]: rename to scheduledDates
// 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.scheduledDays = profile.parseScheduledDays(ctx, j.serviceDays);
}
res.price = profile.parsePrice(ctx, jj);

View file

@ -9,7 +9,7 @@ const parseLoadFactor = (opt, auslastung) => {
if (!auslastung) {
return null;
}
const cls = opt.firstClass === true
const cls = opt.firstClass
? 'KLASSE_1'
: 'KLASSE_2';
const load = auslastung.find(a => a.klasse === cls)?.stufe;

View file

@ -1,14 +1,11 @@
const PARTIAL_FARE_HINT = 'Teilpreis / partial fare';
const parsePrice = (ctx, raw) => {
const p = raw.angebotsPreis || raw.angebote?.preise?.gesamt?.ab || raw.abPreis;
const p = raw.angebotsPreis || raw.angebote?.preise?.gesamt?.ab; // TODO teilpreis
if (p?.betrag) {
const partialFare = raw.hasTeilpreis ?? raw.angebote?.preise?.istTeilpreis ?? raw.teilpreis;
return {
amount: p.betrag,
currency: p.waehrung,
hint: partialFare ? PARTIAL_FARE_HINT : null,
partialFare: partialFare,
hint: null,
};
}
return undefined;
@ -48,7 +45,7 @@ const parseTickets = (ctx, j) => {
partialFare: s.teilpreis,
};
if (s.teilpreis) {
p.addData = PARTIAL_FARE_HINT;
p.addData = 'Teilpreis / partial fare';
}
const conds = s.konditionsAnzeigen || s.konditionen;
if (conds) {

View file

@ -8,14 +8,14 @@
This is an early version. What works:
* `journeys()`, `refreshJourney()` including tickets and bestprice option
* `journeys()`, `refreshJourney()` including tickets
* `locations()`, `nearby()`,
* `departures()`, `arrivals()` boards
* `trip()`
What doesn't work:
* `journeys()` details like stop/station groups, some line details ...
* `journeys()` details like scheduledDays, stop/station groups, some line details ...
* 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).
* some query options/filters (e.g. routingMode for journeys, direction for boards)

View file

@ -99,7 +99,6 @@ const potsdamHbf = '8012666';
const berlinSüdkreuz = '8011113';
const kölnHbf = '8000207';
if (!process.env.VCR_OFF) {
tap.test('journeys  Berlin Schwedter Str. to München Hbf', async (t) => {
const res = await client.journeys(blnSchwedterStr, münchenHbf, {
results: 4,
@ -277,7 +276,6 @@ if (!process.env.VCR_OFF) {
t.end();
});
/*
tap.skip('journeysFromTrip U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => {
const blnMehringdamm = '730939';
@ -395,7 +393,6 @@ tap.skip('journeysFromTrip U Mehringdamm to U Naturkundemuseum, reroute to S
t.end();
});
}
tap.test('departures at Berlin Schwedter Str.', async (t) => {
const res = await client.departures(blnSchwedterStr, {

View file

@ -212,7 +212,6 @@ const dbNavJourney = {
amount: 43.99,
currency: 'EUR',
hint: null,
partialFare: false,
},
tickets: [
{

View file

@ -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$$$$$$',
price: {amount: 31.49, currency: 'EUR', hint: null, partialFare: false},
price: {amount: 31.49, currency: 'EUR', hint: null},
tickets: [{
name: 'from',
priceObj: {

View file

@ -210,7 +210,6 @@ const dbJourney = {
amount: 27.99,
currency: 'EUR',
hint: null,
partialFare: false,
},
tickets: [
{