Fixing issues in 5a0bfd954e

This commit is contained in:
mariusangelmann 2025-07-29 23:52:47 +02:00
parent 45991eb45c
commit 3e666c0b1b
9 changed files with 57 additions and 58 deletions

View file

@ -110,7 +110,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
const ctx = {profile, opt, common, res, userAgent};
let results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries.flat())
.map(res => parse(ctx, res)); // TODO sort?, slice
@ -209,13 +209,13 @@ 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};
const ctx = {profile, opt, common, res, userAgent};
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
.map(j => profile.parseJourney(ctx, j));
const journeys = await Promise.all(verbindungen
.map(j => profile.parseJourney(ctx, j)));
if (opt.bestprice) {
journeys.sort((a, b) => a.price?.amount - b.price?.amount);
}
@ -252,10 +252,10 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken);
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
const ctx = {profile, opt, common, res, userAgent};
return {
journey: profile.parseJourney(ctx, res.verbindungen && res.verbindungen[0] || res),
journey: await profile.parseJourney(ctx, res.verbindungen && res.verbindungen[0] || res),
realtimeDataUpdatedAt: null, // TODO
};
};
@ -280,7 +280,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
const ctx = {profile, opt, common, res, userAgent};
const results = res.map(loc => profile.parseLocation(ctx, loc));
return Number.isInteger(opt.results)
@ -329,7 +329,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatNearbyReq({profile, opt}, location);
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
const ctx = {profile, opt, common, res, userAgent};
const results = res.map(loc => {
const res = profile.parseLocation(ctx, loc);
if (res.latitude || res.location?.latitude) {
@ -361,7 +361,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatTripReq({profile, opt}, id);
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
const ctx = {profile, opt, common, res, userAgent};
const trip = profile.parseTrip(ctx, res, id);

View file

@ -1,23 +1,22 @@
import {formatBaseJourneysReq} from '../p/dbnav/journeys-req.js';
import {getHeaders} from '../p/dbnav/header.js';
// Helper to fetch Verbundticket prices via recon API
const fetchVerbundticketPrices = async (ctx, userAgent, journey) => {
const {profile, opt} = ctx;
// Verbundtickets require a kontext token to fetch prices
const kontext = journey.kontext || journey.ctxRecon;
if (!kontext) {
return null;
}
// Extract journey details
const firstLeg = journey.verbindungsAbschnitte?.[0];
const lastLeg = journey.verbindungsAbschnitte?.[journey.verbindungsAbschnitte.length - 1];
const abgangsOrt = firstLeg?.abgangsOrt;
const ankunftsOrt = lastLeg?.ankunftsOrt;
const abgangsDatum = firstLeg?.abgangsDatum;
// Build recon request exactly matching DB Navigator mobile API format
const reconRequest = {
endpoint: profile.refreshJourneysEndpointTickets || 'https://app.vendo.noncd.db.de/mob/angebote/recon',
@ -26,7 +25,7 @@ const fetchVerbundticketPrices = async (ctx, userAgent, journey) => {
body: {
fahrverguenstigungen: {
nurDeutschlandTicketVerbindungen: ctx.opt.deutschlandTicketConnectionsOnly || false,
deutschlandTicketVorhanden: ctx.opt.deutschlandTicketDiscount || false
deutschlandTicketVorhanden: ctx.opt.deutschlandTicketDiscount || false,
},
reservierungsKontingenteVorhanden: false,
suchParameter: {
@ -37,34 +36,35 @@ const fetchVerbundticketPrices = async (ctx, userAgent, journey) => {
fahrradmitnahme: false,
zielLocationId: ankunftsOrt?.locationId || extractLocationFromKontext(kontext, 'to'),
zeitWunsch: {
reiseDatum: abgangsDatum || new Date().toISOString(),
zeitPunktArt: 'ABFAHRT'
reiseDatum: abgangsDatum || new Date()
.toISOString(),
zeitPunktArt: 'ABFAHRT',
},
verkehrsmittel: ['ALL']
}
verkehrsmittel: ['ALL'],
},
},
einstiegsTypList: ['STANDARD'],
klasse: 'KLASSE_2',
verbindungHin: {
kontext: kontext
kontext: kontext,
},
reisendenProfil: {
reisende: [{
reisendenTyp: 'ERWACHSENER',
ermaessigungen: ['KEINE_ERMAESSIGUNG KLASSENLOS']
}]
}
}
ermaessigungen: ['KEINE_ERMAESSIGUNG KLASSENLOS'],
}],
},
},
};
// Add business customer affiliation if BMIS number is provided
if (ctx.opt.bmisNumber) {
reconRequest.body.firmenZugehoerigkeit = {
bmisNr: ctx.opt.bmisNumber,
identifikationsart: 'BMIS'
identifikationsart: 'BMIS',
};
}
try {
const {res} = await profile.request(ctx, userAgent, reconRequest);
return res;
@ -91,4 +91,4 @@ const extractLocationFromKontext = (kontext, type) => {
export {
fetchVerbundticketPrices,
};
};

View file

@ -1,6 +1,6 @@
import {parseJourney as parseJourneyDefault} from '../../parse/journey.js';
const parseJourney = (ctx, jj) => {
const parseJourney = async (ctx, jj) => {
const legs = (jj.verbindung || jj).verbindungsAbschnitte;
if (legs.length > 0) {
legs[0] = preprocessJourneyLeg(legs[0]);

View file

@ -41,7 +41,7 @@ const trimJourneyId = (journeyId) => {
return journeyId;
};
const parseJourney = (ctx, jj) => { // j = raw journey
const parseJourney = async (ctx, jj) => { // j = raw journey
const {profile, opt} = ctx;
const j = jj.verbindung || jj;
const fallbackLocations = parseLocationsFromCtxRecon(ctx, j);
@ -87,17 +87,16 @@ const parseJourney = (ctx, jj) => { // j = raw journey
const hasVerbundCode = angebote?.verbundCode;
const hasEmptyAngebotsCluster = !angebote?.angebotsCluster || angebote.angebotsCluster.length === 0;
const hasKontext = j.kontext || j.ctxRecon;
// Also check for the warning message that prices need to be fetched
const hasVerbundWarning = angebote?.angebotsMeldungen?.some(msg =>
msg.includes('Verbindung liegt in der Vergangenheit') ||
msg.includes('Preis') ||
msg.includes('Verbund')
const hasVerbundWarning = angebote?.angebotsMeldungen?.some(msg => msg.includes('Verbindung liegt in der Vergangenheit')
|| msg.includes('Preis')
|| msg.includes('Verbund'),
);
if ((hasVerbundCode && hasEmptyAngebotsCluster || hasVerbundWarning) && hasKontext && userAgent && opt.tickets && opt.autoFetchVerbundtickets) {
if ((hasVerbundCode && hasEmptyAngebotsCluster || hasVerbundWarning) && hasKontext && ctx.userAgent && opt.tickets && opt.autoFetchVerbundtickets) {
// Fetch Verbundticket prices via recon API
const reconResult = await fetchVerbundticketPrices(ctx, userAgent, j);
const reconResult = await fetchVerbundticketPrices(ctx, ctx.userAgent, j);
if (reconResult) {
// Use the recon result for price and ticket parsing
if (reconResult.angebote) {

View file

@ -11,7 +11,7 @@ const parsePrice = (ctx, raw) => {
partialFare: partialFare,
};
}
// For Verbundtickets, try to get the lowest price from reiseAngebote
if (raw.reiseAngebote && raw.reiseAngebote.length > 0) {
let lowestPrice = null;
@ -43,7 +43,7 @@ const parsePrice = (ctx, raw) => {
return lowestPrice;
}
}
return undefined;
};
@ -55,7 +55,7 @@ const parseTickets = (ctx, j) => {
let price = parsePrice(ctx, j);
// Handle DB Navigator mobile API format
let ang = j.reiseAngebote;
// If no reiseAngebote, check for angebote.angebotsCluster (DB Navigator mobile format)
if (!ang && j.angebote?.angebotsCluster) {
ang = j.angebote.angebotsCluster.flatMap(c => c.angebotsSubCluster
@ -63,28 +63,28 @@ const parseTickets = (ctx, j) => {
.flatMap(p => {
// Extract all possible ticket types from DB Navigator format
const positions = [];
// Handle verbundAngebot
if (p.verbundAngebot?.reisePosition?.reisePosition) {
const rp = p.verbundAngebot.reisePosition.reisePosition;
rp.teilpreis = Boolean(p.verbundAngebot.reisePosition.teilpreisInformationen?.length);
positions.push(rp);
}
// Handle regular einfacheFahrt
if (p.einfacheFahrt?.standard?.reisePosition) {
const rp = p.einfacheFahrt.standard.reisePosition;
const rp = p.einfacheFahrt.standard.reisePosition.reisePosition || p.einfacheFahrt.standard.reisePosition;
rp.teilpreis = Boolean(p.einfacheFahrt.standard.teilpreisInformationen?.length);
positions.push(rp);
}
// Handle upsell
if (p.einfacheFahrt?.upsellEntgelt?.einfacheFahrt?.reisePosition) {
const rp = p.einfacheFahrt.upsellEntgelt.einfacheFahrt.reisePosition;
const rp = p.einfacheFahrt.upsellEntgelt.einfacheFahrt.reisePosition.reisePosition || p.einfacheFahrt.upsellEntgelt.einfacheFahrt.reisePosition;
rp.teilpreis = Boolean(p.einfacheFahrt.upsellEntgelt.einfacheFahrt.teilpreisInformationen?.length);
positions.push(rp);
}
return positions;
}),
),

View file

@ -26,9 +26,9 @@ const opt = {
products: {},
};
tap.test('parses a refresh journey correctly (DB)', (t) => {
const ctx = {profile, opt, common: null, res};
const journey = profile.parseJourney(ctx, res);
tap.test('parses a refresh journey correctly (DB)', async (t) => {
const ctx = {profile, opt, common: null, res, userAgent: 'test'};
const journey = await profile.parseJourney(ctx, res);
t.same(journey, expected.journey);
t.end();

View file

@ -26,9 +26,9 @@ const opt = {
products: {},
};
tap.test('parses a dbweb journey correctly', (t) => { // TODO DEVI leg
tap.test('parses a dbweb journey correctly', async (t) => { // TODO DEVI leg
const ctx = {profile, opt, common: null, res};
const journey = profile.parseJourney(ctx, res.verbindungen[0]);
const journey = await profile.parseJourney(ctx, res.verbindungen[0]);
t.same(journey, expected);
t.end();

View file

@ -26,9 +26,9 @@ const opt = {
products: {},
};
tap.test('parses a refresh journey correctly (dbweb)', (t) => {
tap.test('parses a refresh journey correctly (dbweb)', async (t) => {
const ctx = {profile, opt, common: null, res};
const journey = profile.parseJourney(ctx, res.verbindungen[0]);
const journey = await profile.parseJourney(ctx, res.verbindungen[0]);
t.same(journey, expected.journey);
t.end();

View file

@ -469,8 +469,8 @@ const input = {
],
}
tap.test('dbnav profile fixes time zone bug', (t) => { // see https://github.com/public-transport/db-vendo-client/issues/24
const parsedJourney = parseJourneyDbnav(ctx, structuredClone(input));
tap.test('dbnav profile fixes time zone bug', async (t) => { // see https://github.com/public-transport/db-vendo-client/issues/24
const parsedJourney = await parseJourneyDbnav(ctx, structuredClone(input));
const expectedDeparture = '2025-03-06T14:52:00+01:00';
t.equal(parsedJourney.legs[0].departure, expectedDeparture)
@ -484,14 +484,14 @@ tap.test('dbnav profile fixes time zone bug', (t) => { // see https://github.com
t.end();
})
tap.test('dbnav profile parses journey without time zone bug like other profiles', (t) => {
tap.test('dbnav profile parses journey without time zone bug like other profiles', async (t) => {
const _input = structuredClone(input);
// fix bug by hand
_input.verbindungsAbschnitte[0].ezAbgangsDatum = '2025-03-06T14:52:00+01:00';
_input.verbindungsAbschnitte[0].ezAnkunftsDatum = '2025-03-06T14:54:00+01:00';
const expected = parseJourneyDefault(ctx, _input)
const expected = await parseJourneyDefault(ctx, _input)
t.same(parseJourneyDbnav(ctx, _input), expected);
t.same(await parseJourneyDbnav(ctx, _input), expected);
t.end();
})