mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-22 22:59:35 +02:00
refreshJourney with tickets, loyaltyCard/firstClass rest support, accept-lang
This commit is contained in:
parent
f379fba930
commit
760a1bdb54
12 changed files with 10884 additions and 50 deletions
26
api.js
26
api.js
|
@ -1,6 +1,30 @@
|
|||
import {createClient} from './index.js';
|
||||
import {profile as dbProfile} from './p/db/index.js';
|
||||
import {createHafasRestApi as createApi} from 'hafas-rest-api';
|
||||
import {loyaltyCardParser} from 'db-rest/lib/loyalty-cards.js';
|
||||
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js';
|
||||
|
||||
const mapRouteParsers = (route, parsers) => {
|
||||
if (!route.includes('journey')) {
|
||||
return parsers;
|
||||
}
|
||||
return {
|
||||
...parsers,
|
||||
loyaltyCard: loyaltyCardParser,
|
||||
firstClass: {
|
||||
description: 'Search for first-class options?',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
parse: parseBoolean,
|
||||
},
|
||||
age: {
|
||||
description: 'Age of traveller',
|
||||
type: 'integer',
|
||||
defaultStr: '*adult*',
|
||||
parse: parseInteger,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const config = {
|
||||
hostname: process.env.HOSTNAME || 'localhost',
|
||||
|
@ -15,8 +39,10 @@ const config = {
|
|||
aboutPage: true,
|
||||
etags: 'strong',
|
||||
csp: 'default-src \'none\' style-src \'self\' \'unsafe-inline\' img-src https:',
|
||||
mapRouteParsers,
|
||||
};
|
||||
|
||||
|
||||
const start = async () => {
|
||||
const vendo = createClient(dbProfile, 'my-hafas-rest-api');
|
||||
const api = await createApi(vendo, config);
|
||||
|
|
12
index.js
12
index.js
|
@ -43,7 +43,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
throw new TypeError('userAgent must be a string');
|
||||
}
|
||||
if (FORBIDDEN_USER_AGENTS.includes(userAgent.toLowerCase())) {
|
||||
throw new TypeError(`userAgent should tell the HAFAS API operators how to contact you. If you have copied "${userAgent}" value from the documentation, please adapt it.`);
|
||||
throw new TypeError(`userAgent should tell the API operators how to contact you. If you have copied "${userAgent}" value from the documentation, please adapt it.`);
|
||||
}
|
||||
|
||||
const _stationBoard = async (station, type, resultsField, parse, opt = {}) => {
|
||||
|
@ -241,17 +241,11 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken);
|
||||
|
||||
const {res, common} = await profile.request({profile, opt}, userAgent, req);
|
||||
if (!Array.isArray(res.outConL) || !res.outConL[0]) {
|
||||
throw new HafasError('invalid response, expected outConL[0]', null, {});
|
||||
}
|
||||
|
||||
const ctx = {profile, opt, common, res};
|
||||
|
||||
return {
|
||||
journey: profile.parseJourney(ctx, res.outConL[0]),
|
||||
realtimeDataUpdatedAt: res.planrtTS && res.planrtTS !== '0'
|
||||
? parseInt(res.planrtTS)
|
||||
: null,
|
||||
journey: profile.parseJourney(ctx, res.verbindungen[0]),
|
||||
realtimeDataUpdatedAt: null, // TODO
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ const checkIfResponseIsOk = (_) => {
|
|||
};
|
||||
|
||||
const request = async (ctx, userAgent, reqData) => {
|
||||
const {profile} = ctx;
|
||||
const {profile, opt} = ctx;
|
||||
|
||||
const endpoint = reqData.endpoint;
|
||||
delete reqData.endpoint;
|
||||
|
@ -110,6 +110,7 @@ const request = async (ctx, userAgent, reqData) => {
|
|||
'Content-Type': 'application/json',
|
||||
'Accept-Encoding': 'gzip, br, deflate',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Language': opt.language || profile.defaultLanguage || 'en',
|
||||
'user-agent': profile.randomizeUserAgent
|
||||
? randomizeUserAgent(userAgent)
|
||||
: userAgent,
|
||||
|
@ -121,7 +122,6 @@ const request = async (ctx, userAgent, reqData) => {
|
|||
});
|
||||
|
||||
const url = endpoint + (reqData.path || '') + '?' + stringify(req.query, {arrayFormat: 'brackets', encodeValuesOnly: true});
|
||||
console.log(url);
|
||||
const reqId = randomBytes(3)
|
||||
.toString('hex');
|
||||
const fetchReq = new Request(url, req);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"journeysEndpoint": "https://int.bahn.de/web/api/angebote/fahrplan",
|
||||
"refreshJourneysEndpoint": "https://int.bahn.de/web/api/angebote/recon",
|
||||
"locationsEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte",
|
||||
"nearbyEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte/nearby",
|
||||
"boardEndpoint": "https://regio-guide.de/@prd/zupo-travel-information/api/public/ri/board/",
|
||||
|
|
|
@ -204,36 +204,20 @@ const transformJourneysQuery = ({profile, opt}, query) => {
|
|||
|
||||
const formatRefreshJourneyReq = (ctx, refreshToken) => {
|
||||
const {profile, opt} = ctx;
|
||||
const req = {
|
||||
getIST: true,
|
||||
getPasslist: Boolean(opt.stopovers),
|
||||
getPolyline: Boolean(opt.polylines),
|
||||
getTariff: Boolean(opt.tickets),
|
||||
let query = {
|
||||
ctxRecon: refreshToken,
|
||||
deutschlandTicketVorhanden: false,
|
||||
nurDeutschlandTicketVerbindungen: false,
|
||||
reservierungsKontingenteVorhanden: false,
|
||||
};
|
||||
if (profile.refreshJourneyUseOutReconL) {
|
||||
req.outReconL = [{ctx: refreshToken}];
|
||||
} else {
|
||||
req.ctxRecon = refreshToken;
|
||||
}
|
||||
req.trfReq = trfReq(opt, true);
|
||||
|
||||
query = Object.assign(query, trfReq(opt, true));
|
||||
return {
|
||||
meth: 'Reconstruction',
|
||||
req,
|
||||
endpoint: profile.refreshJourneysEndpoint,
|
||||
body: query,
|
||||
method: 'post',
|
||||
};
|
||||
};
|
||||
|
||||
// todo: fix this
|
||||
// line: {
|
||||
// type: 'line',
|
||||
// id: '5-vbbbvb-x9',
|
||||
// fahrtNr: '52496',
|
||||
// name: 'X9',
|
||||
// public: true,
|
||||
// mode: 'bus',
|
||||
// product: 'bus',
|
||||
// operator: {type: 'operator', id: 'nahreisezug', name: 'Nahreisezug'}
|
||||
// }
|
||||
const parseLineWithAdditionalName = ({parsed}, l) => {
|
||||
if (l.nameS && ['bus', 'tram', 'ferry'].includes(l.product)) {
|
||||
parsed.name = l.nameS;
|
||||
|
@ -245,11 +229,8 @@ const parseLineWithAdditionalName = ({parsed}, l) => {
|
|||
return parsed;
|
||||
};
|
||||
|
||||
// todo: sotRating, conSubscr, isSotCon, showARSLink, sotCtxt
|
||||
// todo: conSubscr, showARSLink, useableTime
|
||||
const mutateToAddPrice = (parsed, raw) => {
|
||||
parsed.price = null;
|
||||
// TODO find all prices?
|
||||
if (raw.angebotsPreis?.betrag) {
|
||||
parsed.price = {
|
||||
amount: raw.angebotsPreis.betrag,
|
||||
|
@ -260,9 +241,49 @@ const mutateToAddPrice = (parsed, raw) => {
|
|||
return parsed;
|
||||
};
|
||||
|
||||
const mutateToAddTickets = (parsed, opt, j) => {
|
||||
if (!opt.tickets) {
|
||||
return;
|
||||
}
|
||||
if (j.reiseAngebote && j.reiseAngebote.length > 0) { // if refreshJourney()
|
||||
parsed.tickets = j.reiseAngebote
|
||||
.filter(s => s.typ == 'REISEANGEBOT' && !s.angebotsbeziehungList.flatMap(b => b.referenzen)
|
||||
.find(r => r.referenzAngebotsoption == 'PFLICHT'))
|
||||
.map((s) => {
|
||||
return {
|
||||
name: s.name,
|
||||
priceObj: {
|
||||
amount: Math.round(s.preis?.betrag * 100),
|
||||
currency: s.preis?.waehrung,
|
||||
},
|
||||
addData: s.teilpreis ? 'Teilpreis / partial fare' : undefined,
|
||||
addDataTicketInfo: s.konditionsAnzeigen?.map(a => a.anzeigeUeberschrift)
|
||||
.join('. '),
|
||||
addDataTicketDetails: s.konditionsAnzeigen?.map(a => a.textLang)
|
||||
.join(' '),
|
||||
addDataTravelInfo: s.leuchtturmInfo?.text,
|
||||
firstClass: s.klasse == 'KLASSE_1',
|
||||
partialFare: s.teilpreis,
|
||||
};
|
||||
});
|
||||
if (opt.generateUnreliableTicketUrls) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} else if (j.angebotsPreis?.betrag) { // if journeys()
|
||||
parsed.tickets = [{
|
||||
name: 'from',
|
||||
priceObj: {
|
||||
amount: Math.round(j.angebotsPreis.betrag * 100),
|
||||
currency: j.angebotsPreis.waehrung,
|
||||
},
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
const parseJourneyWithPriceAndTickets = ({parsed, opt}, raw) => {
|
||||
mutateToAddPrice(parsed, raw);
|
||||
// mutateToAddTickets(parsed, opt, raw); TODO
|
||||
mutateToAddTickets(parsed, opt, raw);
|
||||
return parsed;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
const c = {
|
||||
NONE: Symbol('no loyalty card'),
|
||||
BAHNCARD: Symbol('Bahncard'),
|
||||
VORTEILSCARD: Symbol('VorteilsCard'),
|
||||
HALBTAXABO: Symbol('HalbtaxAbo'),
|
||||
VOORDEELURENABO: Symbol('Voordeelurenabo'),
|
||||
SHCARD: Symbol('SH-Card'),
|
||||
GENERALABONNEMENT: Symbol('General-Abonnement'),
|
||||
};
|
||||
import {data as c} from 'hafas-client/p/db/loyalty-cards.js'; // TODO remove hafas-client dep?
|
||||
|
||||
// see https://gist.github.com/juliuste/202bb04f450a79f8fa12a2ec3abcd72d
|
||||
const formatLoyaltyCard = (data) => {
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"cross-fetch": "^4.0.0",
|
||||
"google-polyline": "^1.0.3",
|
||||
"gps-distance": "0.0.4",
|
||||
"hafas-client": "^6.3.2",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"lodash": "^4.17.5",
|
||||
"luxon": "^3.1.1",
|
||||
|
@ -73,6 +74,7 @@
|
|||
"@pollyjs/core": "^6.0.5",
|
||||
"@pollyjs/persister-fs": "^6.0.5",
|
||||
"@stylistic/eslint-plugin": "^1.5.1",
|
||||
"db-rest": "github:derhuerst/db-rest",
|
||||
"eslint": "^8.56.0",
|
||||
"hafas-rest-api": "^5.1.3",
|
||||
"is-coordinates": "^2.0.2",
|
||||
|
|
|
@ -2,10 +2,11 @@ import slugg from 'slugg';
|
|||
|
||||
const parseLine = (ctx, p) => {
|
||||
const profile = ctx.profile;
|
||||
const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no;
|
||||
const res = {
|
||||
type: 'line',
|
||||
id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription || p.train?.no), // TODO terrible
|
||||
fahrtNr: (p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no)+'',
|
||||
fahrtNr: fahrtNr ? String(fahrtNr) : undefined,
|
||||
name: p.verkehrsmittel?.name || p.zugName || p.transport?.journeyDescription || p.train && p.train.category + ' ' + p.train.lineName,
|
||||
public: true,
|
||||
};
|
||||
|
|
40
test/db-refresh-journey.js
Normal file
40
test/db-refresh-journey.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
// todo: use import assertions once they're supported by Node.js & ESLint
|
||||
// https://github.com/tc39/proposal-import-assertions
|
||||
import {createRequire} from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
import tap from 'tap';
|
||||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/db/index.js';
|
||||
const res = require('./fixtures/db-refresh-journey.json');
|
||||
import {dbJourney as expected} from './fixtures/db-refresh-journey.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test');
|
||||
const {profile} = client;
|
||||
|
||||
const opt = {
|
||||
results: null,
|
||||
via: null,
|
||||
stopovers: false,
|
||||
transfers: -1,
|
||||
transferTime: 0,
|
||||
accessibility: 'none',
|
||||
bike: false,
|
||||
tickets: true,
|
||||
polylines: true,
|
||||
remarks: true,
|
||||
walkingSpeed: 'normal',
|
||||
startWithWalking: true,
|
||||
scheduledDays: false,
|
||||
departure: '2020-04-10T20:33+02:00',
|
||||
products: {},
|
||||
};
|
||||
|
||||
tap.test('parses a refresh journey correctly (DB)', (t) => {
|
||||
const ctx = {profile, opt, common: null, res};
|
||||
const journey = profile.parseJourney(ctx, res.verbindungen[0]);
|
||||
|
||||
t.same(journey, expected.journey);
|
||||
t.end();
|
||||
});
|
7
test/fixtures/db-journey.js
vendored
7
test/fixtures/db-journey.js
vendored
|
@ -283,6 +283,13 @@ const dbJourney = {
|
|||
],
|
||||
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$$$$$$¶KC¶#VE#2#CF#100#CA#0#CM#0#SICT#0#AM#81#AM2#0#RT#7#¶KCC¶I1ZFIzEjRVJHIzMjSElOIzAjRUNLIzcwNTkxMXw3MDU5MTF8NzA2MTM4fDcwNjEzOHwwfDB8NDg1fDcwNTg5N3wxfDB8MTh8MHwwfC0yMTQ3NDgzNjQ4I0dBTSMxMTA0MjUwNTExIwpaI1ZOIzEjU1QjMTczMzE3MzczMSNQSSMxI1pJIzE2MTQ3MyNUQSMxI0RBIzExMDQyNSMxUyM4MDAwMjA4IzFUIzUwNCNMUyM4MDAyNzUzI0xUIzU0NSNQVSM4MSNSVCMxI0NBI3MjWkUjMTIjWkIjUyAgICAgMTIjUEMjNCNGUiM4MDAwMjA3I0ZUIzUxMSNUTyM4MDAzMzY4I1RUIzUxMiMKRiNWTiMwI1NUIzE3MzMxNzM3MzEjUEkjMSNQVSM4MSNaSSMyMjgzODI4ODkzI0RBIzExMDQyNSNGUiM4MDAzMzY4I1RPIzgwNzMzNjgjRlQjNTEyI1RUIzUxOSNUUyMwI0ZGIyNGViMwIwpaI1ZOIzEjU1QjMTczMzE3MzczMSNQSSMxI1pJIzE1NTA2MyNUQSMwI0RBIzExMDQyNSMxUyM4MDAwMDgwIzFUIzM1OCNMUyM4MDAwMjYxI0xUIzEwMDYjUFUjODEjUlQjMSNDQSNJQ0UjWkUjNTIzI1pCI0lDRSAgNTIzI1BDIzAjRlIjODA3MzM2OCNGVCM1MjAjVE8jODAwMDI4NCNUVCM4NTgj¶KRCC¶#VE#1#¶SC¶1_H4sIAAAAAAACA32P306DMBjFX8X0GpevhUIhIUFGFv8sGzHOaIwXbHQTU2CWskgIz+GbeOXdXswCemE09qLpOT09v68tOnCJPIQnDkMG4q9Kiyic3EYTV2vJX5DXoqLOZ8ijRn8IkQcGKmsVJYrrMAFCwcIYDeZNlvcmUNBLW9uh4RQb6LloZkLJOfIeWqSafR+Lr5eRDuVl2quLxVSLQyLqXmEgJuoeh5mmT7uxWJNTvp+Xm7FGZKlOnvk4WPpXx3dRnJyvt8Gdb7uUOSYE9z4F1zKBuMHKZxDM9QZAwAlC/WbvY8c0scNsmwSZvzq+ATDA1KIs0INUavzgbJgikfJP7OL4IYs1l7svNMbAiMtczbZcy6I2pj/YzPqHTQh2zd/sHVdxKRqRFdpTsuaDdVnWsuBNWNZFWiFvm4hqvIiTqhJZpb6zfFPGiUxyHWq7rvsE0LytQvMBAAA=',
|
||||
price: {amount: 31.49, currency: 'EUR', hint: null},
|
||||
tickets: [{
|
||||
name: 'from',
|
||||
priceObj: {
|
||||
amount: 3149,
|
||||
currency: 'EUR',
|
||||
},
|
||||
}],
|
||||
remarks: [],
|
||||
};
|
||||
|
||||
|
|
265
test/fixtures/db-refresh-journey.js
vendored
Normal file
265
test/fixtures/db-refresh-journey.js
vendored
Normal file
|
@ -0,0 +1,265 @@
|
|||
const dbJourney = {
|
||||
journey: {
|
||||
type: 'journey',
|
||||
legs: [
|
||||
{
|
||||
origin: {
|
||||
type: 'stop',
|
||||
id: '8011160',
|
||||
name: 'Berlin Hbf',
|
||||
location: {
|
||||
type: 'location',
|
||||
id: '8011160',
|
||||
latitude: 52.525589,
|
||||
longitude: 13.369549,
|
||||
},
|
||||
},
|
||||
destination: {
|
||||
type: 'stop',
|
||||
id: '8000080',
|
||||
name: 'Dortmund Hbf',
|
||||
location: {
|
||||
type: 'location',
|
||||
id: '8000080',
|
||||
latitude: 51.517899,
|
||||
longitude: 7.459294,
|
||||
},
|
||||
},
|
||||
departure: '2024-12-18T00:22:00+01:00',
|
||||
plannedDeparture: '2024-12-18T00:22:00+01:00',
|
||||
departureDelay: null,
|
||||
arrival: '2024-12-18T05:21:00+01:00',
|
||||
plannedArrival: '2024-12-18T05:21:00+01:00',
|
||||
arrivalDelay: null,
|
||||
tripId: '2|#VN#1#ST#1733173731#PI#1#ZI#153019#TA#0#DA#181224#1S#8010255#1T#11#LS#8500010#LT#1048#PU#81#RT#1#CA#ICE#ZE#101#ZB#ICE 101#PC#0#FR#8010255#FT#11#TO#8500010#TT#1048#',
|
||||
line: {
|
||||
type: 'line',
|
||||
id: 'ice-101',
|
||||
fahrtNr: '101',
|
||||
name: 'ICE 101',
|
||||
public: true,
|
||||
productName: 'ICE',
|
||||
mode: 'train',
|
||||
product: 'nationalExpress',
|
||||
operator: {
|
||||
type: 'operator',
|
||||
id: 'db-fernverkehr-ag',
|
||||
name: 'DB Fernverkehr AG',
|
||||
},
|
||||
},
|
||||
direction: 'KÖLN',
|
||||
arrivalPlatform: '16',
|
||||
plannedArrivalPlatform: '16',
|
||||
departurePlatform: '13',
|
||||
plannedDeparturePlatform: '13',
|
||||
remarks: [
|
||||
{
|
||||
text: 'Bicycles conveyed - subject to reservation',
|
||||
type: 'hint',
|
||||
code: 'bicycle-conveyance-reservation',
|
||||
summary: 'bicycles conveyed, subject to reservation',
|
||||
},
|
||||
{
|
||||
text: 'Number of bicycles conveyed limited',
|
||||
type: 'hint',
|
||||
code: 'bicycle-conveyance',
|
||||
summary: 'bicycles conveyed',
|
||||
},
|
||||
{
|
||||
text: 'Komfort Check-in possible (visit bahn.de/kci for more information)',
|
||||
type: 'hint',
|
||||
code: 'komfort-checkin',
|
||||
summary: 'Komfort-Checkin available',
|
||||
},
|
||||
{
|
||||
text: 'vehicle-mounted access aid',
|
||||
type: 'hint',
|
||||
code: 'boarding-ramp',
|
||||
summary: 'vehicle-mounted boarding ramp available',
|
||||
},
|
||||
{
|
||||
code: 'text.journeystop.product.or.direction.changes.journey.message',
|
||||
summary: 'From Minden(Westf) as ICE 101 heading towards Basel SBB',
|
||||
text: 'From Minden(Westf) as ICE 101 heading towards Basel SBB',
|
||||
type: 'hint',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
origin: {
|
||||
type: 'stop',
|
||||
id: '8000080',
|
||||
name: 'Dortmund Hbf',
|
||||
location: {
|
||||
type: 'location',
|
||||
id: '8000080',
|
||||
latitude: 51.517899,
|
||||
longitude: 7.459294,
|
||||
},
|
||||
},
|
||||
destination: {
|
||||
type: 'stop',
|
||||
id: '8000207',
|
||||
name: 'Köln Hbf',
|
||||
location: {
|
||||
type: 'location',
|
||||
id: '8000207',
|
||||
latitude: 50.943029,
|
||||
longitude: 6.95873,
|
||||
},
|
||||
},
|
||||
departure: '2024-12-18T05:36:00+01:00',
|
||||
plannedDeparture: '2024-12-18T05:36:00+01:00',
|
||||
departureDelay: null,
|
||||
arrival: '2024-12-18T06:47:00+01:00',
|
||||
plannedArrival: '2024-12-18T06:47:00+01:00',
|
||||
arrivalDelay: null,
|
||||
tripId: '2|#VN#1#ST#1733173731#PI#1#ZI#154039#TA#0#DA#181224#1S#8000080#1T#536#LS#8000096#LT#1024#PU#81#RT#1#CA#IC#ZE#2040#ZB#IC 2040#PC#1#FR#8000080#FT#536#TO#8000096#TT#1024#',
|
||||
line: {
|
||||
type: 'line',
|
||||
id: 'ic-2040',
|
||||
fahrtNr: '2040',
|
||||
name: 'IC 2040',
|
||||
public: true,
|
||||
productName: 'IC',
|
||||
mode: 'train',
|
||||
product: 'national',
|
||||
operator: {
|
||||
type: 'operator',
|
||||
id: 'db-fernverkehr-ag',
|
||||
name: 'DB Fernverkehr AG',
|
||||
},
|
||||
},
|
||||
direction: 'Stuttgart Hbf',
|
||||
arrivalPlatform: '7 D-G',
|
||||
plannedArrivalPlatform: '7 D-G',
|
||||
departurePlatform: '11',
|
||||
plannedDeparturePlatform: '11',
|
||||
remarks: [
|
||||
{
|
||||
text: 'Bicycles conveyed - subject to reservation',
|
||||
type: 'hint',
|
||||
code: 'bicycle-conveyance-reservation',
|
||||
summary: 'bicycles conveyed, subject to reservation',
|
||||
},
|
||||
{
|
||||
text: 'Number of bicycles conveyed limited',
|
||||
type: 'hint',
|
||||
code: 'bicycle-conveyance',
|
||||
summary: 'bicycles conveyed',
|
||||
},
|
||||
{
|
||||
text: 'Komfort Check-in possible (visit bahn.de/kci for more information)',
|
||||
type: 'hint',
|
||||
code: 'komfort-checkin',
|
||||
summary: 'Komfort-Checkin available',
|
||||
},
|
||||
{
|
||||
code: 'RZ',
|
||||
summary: 'Einstieg mit Rollstuhl stufenfrei',
|
||||
text: 'Einstieg mit Rollstuhl stufenfrei',
|
||||
type: 'hint',
|
||||
},
|
||||
{
|
||||
text: 'Intercity 2: visit www.bahn.de/ic2 for more information',
|
||||
type: 'hint',
|
||||
code: 'intercity-2',
|
||||
summary: 'Intercity 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
refreshToken: 'T$A=1@O=Berlin Hbf@X=13369549@Y=52525589@L=8011160@a=128@$A=1@O=Dortmund Hbf@X=7459294@Y=51517899@L=8000080@a=128@$202412180022$202412180521$ICE 101$$1$$$$$$§T$A=1@O=Dortmund Hbf@X=7459294@Y=51517899@L=8000080@a=128@$A=1@O=Köln Hbf@X=6958730@Y=50943029@L=8000207@a=128@$202412180536$202412180647$IC 2040$$1$$$$$$',
|
||||
remarks: [],
|
||||
price: {
|
||||
amount: 27.99,
|
||||
currency: 'EUR',
|
||||
hint: null,
|
||||
},
|
||||
tickets: [
|
||||
{
|
||||
name: 'Super Sparpreis',
|
||||
priceObj: {
|
||||
amount: 2799,
|
||||
currency: 'EUR',
|
||||
},
|
||||
addData: undefined,
|
||||
addDataTicketInfo: 'Train-specific travel. No cancellations',
|
||||
addDataTicketDetails: 'You can use all trains indicated on your ticket. Your ticket constitutes a continuous contract of carriage in each direction (through ticket). Should you make a passenger rights claim, the ticket will be considered in its entirety. Special rules apply to tickets including City-Ticket, see there. Your ticket cannot be cancelled.',
|
||||
addDataTravelInfo: undefined,
|
||||
firstClass: false,
|
||||
partialFare: false,
|
||||
},
|
||||
{
|
||||
name: 'Super Sparpreis',
|
||||
priceObj: {
|
||||
amount: 3599,
|
||||
currency: 'EUR',
|
||||
},
|
||||
addData: undefined,
|
||||
addDataTicketInfo: 'Train-specific travel. No cancellations. No access to the DB Lounge',
|
||||
addDataTicketDetails: 'You can use all trains indicated on your ticket. Your ticket constitutes a continuous contract of carriage in each direction (through ticket). Should you make a passenger rights claim, the ticket will be considered in its entirety. Special rules apply to tickets including City-Ticket, see there. Your ticket cannot be cancelled. Your ticket does not entitle you to use the DB Lounge.',
|
||||
addDataTravelInfo: 'Travel 1st class',
|
||||
firstClass: true,
|
||||
partialFare: false,
|
||||
},
|
||||
{
|
||||
name: 'Sparpreis',
|
||||
priceObj: {
|
||||
amount: 3499,
|
||||
currency: 'EUR',
|
||||
},
|
||||
addData: undefined,
|
||||
addDataTicketInfo: 'Train-specific travel. Cancellation subject to a fee before first day of validity',
|
||||
addDataTicketDetails: 'You can use all trains indicated on your ticket. Your ticket constitutes a continuous contract of carriage in each direction (through ticket). Should you make a passenger rights claim, the ticket will be considered in its entirety. Special rules apply to tickets including City-Ticket, see there. You can cancel your ticket up to and including 17.12.2024 for a fee of EUR 10,00. You will receive a voucher for the remaining amount. No cancellation thereafter.',
|
||||
addDataTravelInfo: undefined,
|
||||
firstClass: false,
|
||||
partialFare: false,
|
||||
},
|
||||
{
|
||||
name: 'Sparpreis',
|
||||
priceObj: {
|
||||
amount: 4499,
|
||||
currency: 'EUR',
|
||||
},
|
||||
addData: undefined,
|
||||
addDataTicketInfo: 'Train-specific travel. Cancellation subject to a fee before first day of validity. No access to the DB Lounge',
|
||||
addDataTicketDetails: 'You can use all trains indicated on your ticket. Your ticket constitutes a continuous contract of carriage in each direction (through ticket). Should you make a passenger rights claim, the ticket will be considered in its entirety. Special rules apply to tickets including City-Ticket, see there. You can cancel your ticket up to and including 17.12.2024 for a fee of EUR 10,00. You will receive a voucher for the remaining amount. No cancellation thereafter. Your ticket does not entitle you to use the DB Lounge.',
|
||||
addDataTravelInfo: 'Travel 1st class',
|
||||
firstClass: true,
|
||||
partialFare: false,
|
||||
},
|
||||
{
|
||||
name: 'Flexpreis',
|
||||
priceObj: {
|
||||
amount: 13280,
|
||||
currency: 'EUR',
|
||||
},
|
||||
addData: undefined,
|
||||
addDataTicketInfo: 'Unrestricted choice of trains. Cancellation free of charge before first day of validity. City-Ticket',
|
||||
addDataTicketDetails: 'Your ICE ticket lets you take any type of train. Your ticket constitutes a continuous contract of carriage in each direction (through ticket). Should you make a passenger rights claim, the ticket will be considered in its entirety. Special rules apply to tickets including City-Ticket; see there. You can cancel your ticket free of charge up to and including 17.12.2024. After that, cancellation is available for a fee of EUR 19,00. Your ticket includes a City-Ticket for Berlin, Tarifbereiche A und B and Köln, Stadtgebiet Köln (Tarifgebiet 2100). The City-Ticket is valid in conjunction with your long-distance ticket only when you use it for connecting services in local or regional rail passenger transport (e.g. S-Bahn, RE and RB trains) as part of the through ticket. The City-Ticket is issued together with your Super Sparpreis or Sparpreis ticket, depending on the journey you have booked.',
|
||||
addDataTravelInfo: undefined,
|
||||
firstClass: false,
|
||||
partialFare: false,
|
||||
},
|
||||
{
|
||||
name: 'Flexpreis',
|
||||
priceObj: {
|
||||
amount: 23910,
|
||||
currency: 'EUR',
|
||||
},
|
||||
addData: undefined,
|
||||
addDataTicketInfo: 'Unrestricted choice of trains. Cancellation free of charge before first day of validity. City-Ticket. Access to the DB Lounge. Seat included',
|
||||
addDataTicketDetails: 'Your ICE ticket lets you take any type of train. Your ticket constitutes a continuous contract of carriage in each direction (through ticket). Should you make a passenger rights claim, the ticket will be considered in its entirety. Special rules apply to tickets including City-Ticket; see there. You can cancel your ticket free of charge up to and including 17.12.2024. After that, cancellation is available for a fee of EUR 19,00. Your ticket includes a City-Ticket for Berlin, Tarifbereiche A und B and Köln, Stadtgebiet Köln (Tarifgebiet 2100). The City-Ticket is valid in conjunction with your long-distance ticket only when you use it for connecting services in local or regional rail passenger transport (e.g. S-Bahn, RE and RB trains) as part of the through ticket. The City-Ticket is issued together with your Super Sparpreis or Sparpreis ticket, depending on the journey you have booked. Your ticket entitles you to use the DB Lounge. Your ticket includes a free seat reservation.',
|
||||
addDataTravelInfo: 'Travel 1st class',
|
||||
firstClass: true,
|
||||
partialFare: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
realtimeDataUpdatedAt: null,
|
||||
};
|
||||
|
||||
export {
|
||||
dbJourney,
|
||||
};
|
10485
test/fixtures/db-refresh-journey.json
vendored
Normal file
10485
test/fixtures/db-refresh-journey.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue