2024-02-06 22:58:49 +01:00
import isObj from 'lodash/isObject.js' ;
import sortBy from 'lodash/sortBy.js' ;
import omit from 'lodash/omit.js' ;
2024-12-17 19:41:00 +00:00
import distance from 'gps-distance' ;
2016-06-22 01:39:04 +02:00
2024-02-06 22:58:49 +01:00
import { defaultProfile } from './lib/default-profile.js' ;
import { validateProfile } from './lib/validate-profile.js' ;
import { INVALID _REQUEST } from './lib/errors.js' ;
import { sliceLeg } from './lib/slice-leg.js' ;
import { HafasError } from './lib/errors.js' ;
2016-06-22 02:09:02 +02:00
2023-03-14 21:00:20 +01:00
// background info: https://github.com/public-transport/hafas-client/issues/286
const FORBIDDEN _USER _AGENTS = [
'my-awesome-program' , // previously used in readme.md, p/*/readme.md & docs/*.md
'hafas-client-example' , // previously used in p/*/example.js
'link-to-your-project-or-email' , // now used throughout
2024-02-06 22:58:49 +01:00
] ;
2023-03-14 21:00:20 +01:00
2024-02-06 22:58:49 +01:00
const isNonEmptyString = str => 'string' === typeof str && str . length > 0 ;
2018-02-28 16:09:23 +01:00
2018-08-25 01:52:46 +02:00
const validateLocation = ( loc , name = 'location' ) => {
if ( ! isObj ( loc ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( name + ' must be an object.' ) ;
2018-08-25 01:52:46 +02:00
} else if ( loc . type !== 'location' ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'invalid location object.' ) ;
2018-08-25 01:52:46 +02:00
} else if ( 'number' !== typeof loc . latitude ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( name + '.latitude must be a number.' ) ;
2018-08-25 01:52:46 +02:00
} else if ( 'number' !== typeof loc . longitude ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( name + '.longitude must be a number.' ) ;
2018-08-25 01:52:46 +02:00
}
2024-02-06 22:58:49 +01:00
} ;
2018-08-25 01:52:46 +02:00
2020-03-09 20:54:43 +01:00
const validateWhen = ( when , name = 'when' ) => {
2024-02-06 22:58:49 +01:00
if ( Number . isNaN ( Number ( when ) ) ) {
throw new TypeError ( name + ' is invalid' ) ;
2020-03-09 20:54:43 +01:00
}
2024-02-06 22:58:49 +01:00
} ;
2020-03-09 20:54:43 +01:00
2019-09-28 22:15:22 +02:00
const createClient = ( profile , userAgent , opt = { } ) => {
2024-02-06 22:58:49 +01:00
profile = Object . assign ( { } , defaultProfile , profile ) ;
validateProfile ( profile ) ;
2017-10-03 17:36:42 +02:00
2018-07-19 21:50:20 +02:00
if ( 'string' !== typeof userAgent ) {
2019-02-13 17:55:57 +01:00
throw new TypeError ( 'userAgent must be a string' ) ;
2018-07-19 21:50:20 +02:00
}
2023-03-14 21:00:20 +01:00
if ( FORBIDDEN _USER _AGENTS . includes ( userAgent . toLowerCase ( ) ) ) {
2024-12-11 23:51:58 +00:00
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. ` ) ;
2023-03-14 21:00:20 +01:00
}
2018-07-19 21:50:20 +02:00
2021-12-29 21:11:07 +01:00
const _stationBoard = async ( station , type , resultsField , parse , opt = { } ) => {
2024-12-07 22:46:04 +00:00
if ( isObj ( station ) && station . id ) {
station = station . id ;
} else if ( 'string' !== typeof station ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'station must be an object or a string.' ) ;
}
2017-11-12 18:06:16 +01:00
2018-06-26 15:49:50 +02:00
if ( 'string' !== typeof type || ! type ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'type must be a non-empty string.' ) ;
2018-06-26 15:49:50 +02:00
}
2024-02-06 22:58:49 +01:00
if ( ! profile . departuresGetPasslist && 'stopovers' in opt ) {
throw new Error ( 'opt.stopovers is not supported by this endpoint' ) ;
2018-12-30 15:21:04 +01:00
}
2024-02-06 22:58:49 +01:00
if ( ! profile . departuresStbFltrEquiv && 'includeRelatedStations' in opt ) {
throw new Error ( 'opt.includeRelatedStations is not supported by this endpoint' ) ;
2018-12-30 15:21:04 +01:00
}
2017-11-12 18:06:16 +01:00
opt = Object . assign ( {
2019-10-28 17:42:33 +01:00
// todo: for arrivals(), this is actually a station it *has already* stopped by
direction : null , // only show departures stopping by this station
2020-03-09 21:59:17 +01:00
line : null , // filter by line ID
2018-06-28 13:00:33 +02:00
duration : 10 , // show departures for the next n minutes
2020-03-25 21:54:38 +01:00
results : null , // max. number of results; `null` means "whatever HAFAS wants"
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2019-01-23 13:03:01 +08:00
linesOfStops : false , // parse & expose lines at the stop/station?
2018-07-10 18:59:54 +02:00
remarks : true , // parse & expose hints & warnings?
2018-12-27 20:17:23 +01:00
stopovers : false , // fetch & parse previous/next stopovers?
2018-07-10 18:59:54 +02:00
// departures at related stations
// e.g. those that belong together on the metro map.
2024-02-06 22:58:49 +01:00
includeRelatedStations : true ,
} , opt ) ;
opt . when = new Date ( opt . when || Date . now ( ) ) ;
if ( Number . isNaN ( Number ( opt . when ) ) ) {
throw new Error ( 'opt.when is invalid' ) ;
}
2019-10-20 01:47:09 +02:00
2024-12-07 22:46:04 +00:00
const req = profile . formatStationBoardReq ( { profile , opt } , station , resultsField ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
2019-10-20 01:47:09 +02:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2024-12-10 17:51:20 +00:00
const results = ( res [ resultsField ] || res . items ) . map ( res => parse ( ctx , res ) ) ; // todo sort?
2021-12-29 21:11:07 +01:00
return {
[ resultsField ] : results ,
2024-12-07 22:46:04 +00:00
realtimeDataUpdatedAt : null , // TODO
2024-02-06 22:58:49 +01:00
} ;
} ;
2017-11-12 18:06:16 +01:00
2021-12-29 18:53:50 +01:00
const departures = async ( station , opt = { } ) => {
2024-02-06 22:58:49 +01:00
return await _stationBoard ( station , 'DEP' , 'departures' , profile . parseDeparture , opt ) ;
} ;
2021-12-29 18:53:50 +01:00
const arrivals = async ( station , opt = { } ) => {
2024-02-06 22:58:49 +01:00
return await _stationBoard ( station , 'ARR' , 'arrivals' , profile . parseArrival , opt ) ;
} ;
2018-06-26 17:11:25 +02:00
2021-12-29 18:53:50 +01:00
const journeys = async ( from , to , opt = { } ) => {
2024-02-06 22:58:49 +01:00
from = profile . formatLocation ( profile , from , 'from' ) ;
to = profile . formatLocation ( profile , to , 'to' ) ;
2017-11-12 20:02:32 +01:00
2024-02-06 22:58:49 +01:00
if ( 'earlierThan' in opt && 'laterThan' in opt ) {
throw new TypeError ( 'opt.earlierThan and opt.laterThan are mutually exclusive.' ) ;
2018-05-30 16:15:26 +02:00
}
2024-02-06 22:58:49 +01:00
if ( 'departure' in opt && 'arrival' in opt ) {
throw new TypeError ( 'opt.departure and opt.arrival are mutually exclusive.' ) ;
2018-03-04 19:53:53 +01:00
}
2024-02-06 22:58:49 +01:00
let journeysRef = null ;
2018-03-04 19:53:53 +01:00
if ( 'earlierThan' in opt ) {
if ( ! isNonEmptyString ( opt . earlierThan ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'opt.earlierThan must be a non-empty string.' ) ;
2018-03-04 19:53:53 +01:00
}
2024-02-06 22:58:49 +01:00
if ( 'departure' in opt || 'arrival' in opt ) {
throw new TypeError ( 'opt.earlierThan and opt.departure/opt.arrival are mutually exclusive.' ) ;
2018-03-04 19:53:53 +01:00
}
2024-02-06 22:58:49 +01:00
journeysRef = opt . earlierThan ;
2018-03-04 19:53:53 +01:00
}
if ( 'laterThan' in opt ) {
if ( ! isNonEmptyString ( opt . laterThan ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'opt.laterThan must be a non-empty string.' ) ;
2018-03-04 19:53:53 +01:00
}
2024-02-06 22:58:49 +01:00
if ( 'departure' in opt || 'arrival' in opt ) {
throw new TypeError ( 'opt.laterThan and opt.departure/opt.arrival are mutually exclusive.' ) ;
2018-03-04 19:53:53 +01:00
}
2024-02-06 22:58:49 +01:00
journeysRef = opt . laterThan ;
2018-03-04 19:53:53 +01:00
}
2017-11-12 20:02:32 +01:00
opt = Object . assign ( {
2020-02-27 15:57:54 +01:00
results : null , // number of journeys – `null` means "whatever HAFAS returns"
2017-11-12 20:02:32 +01:00
via : null , // let journeys pass this station?
2018-06-13 20:39:33 +02:00
stopovers : false , // return stations on the way?
2024-12-07 18:29:16 +00:00
transfers : null , // maximum nr of transfers
2017-11-12 20:02:32 +01:00
transferTime : 0 , // minimum time for a single transfer in minutes
// todo: does this work with every endpoint?
accessibility : 'none' , // 'none', 'partial' or 'complete'
bike : false , // only bike-friendly journeys
2018-10-25 16:11:19 +02:00
walkingSpeed : 'normal' , // 'slow', 'normal', 'fast'
2018-06-25 18:33:22 +02:00
// Consider walking to nearby stations at the beginning of a journey?
2018-07-23 20:42:22 +02:00
startWithWalking : true ,
2020-05-21 17:31:51 +02:00
tickets : false , // return tickets?
polylines : false , // return leg shapes?
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2020-05-21 17:31:51 +02:00
remarks : true , // parse & expose hints & warnings?
2022-10-24 17:30:05 +02:00
scheduledDays : false , // parse & expose dates each journey is valid on?
2024-02-06 22:58:49 +01:00
} , opt ) ;
if ( opt . via ) {
opt . via = profile . formatLocation ( profile , opt . via , 'opt.via' ) ;
}
2018-05-28 20:34:24 +02:00
if ( opt . when !== undefined ) {
2024-02-06 22:58:49 +01:00
throw new Error ( 'opt.when is not supported anymore. Use opt.departure/opt.arrival.' ) ;
2018-05-28 20:34:24 +02:00
}
2024-02-06 22:58:49 +01:00
let when = new Date ( ) , outFrwd = true ;
2018-05-28 20:34:24 +02:00
if ( opt . departure !== undefined && opt . departure !== null ) {
2024-02-06 22:58:49 +01:00
when = new Date ( opt . departure ) ;
if ( Number . isNaN ( Number ( when ) ) ) {
throw new TypeError ( 'opt.departure is invalid' ) ;
}
2018-05-28 20:34:24 +02:00
} else if ( opt . arrival !== undefined && opt . arrival !== null ) {
2019-12-29 22:16:45 +01:00
if ( ! profile . journeysOutFrwd ) {
2024-02-06 22:58:49 +01:00
throw new Error ( 'opt.arrival is unsupported' ) ;
}
when = new Date ( opt . arrival ) ;
if ( Number . isNaN ( Number ( when ) ) ) {
throw new TypeError ( 'opt.arrival is invalid' ) ;
2019-12-29 22:16:45 +01:00
}
2024-02-06 22:58:49 +01:00
outFrwd = false ;
2018-05-28 20:34:24 +02:00
}
2017-12-18 20:01:12 +01:00
2024-12-07 16:16:31 +00:00
const filters = profile . formatProductsFilter ( { profile } , opt . products || { } ) ;
// TODO opt.accessibility
2018-10-25 16:11:19 +02:00
2020-07-19 22:58:12 +02:00
const query = {
2024-12-07 18:29:16 +00:00
maxUmstiege : opt . transfers ,
minUmstiegszeit : opt . transferTime ,
2024-12-07 16:16:31 +00:00
deutschlandTicketVorhanden : false ,
nurDeutschlandTicketVerbindungen : false ,
reservierungsKontingenteVorhanden : false ,
schnelleVerbindungen : true ,
sitzplatzOnly : false ,
abfahrtsHalt : from . lid ,
2024-12-07 18:29:16 +00:00
zwischenhalte : opt . via
2024-12-17 19:41:00 +00:00
? [ { id : opt . via . lid } ]
2024-12-07 18:29:16 +00:00
: null ,
2024-12-07 16:16:31 +00:00
ankunftsHalt : to . lid ,
produktgattungen : filters ,
bikeCarriage : opt . bike ,
// TODO
2020-07-19 22:58:12 +02:00
// todo: this is actually "take additional stations nearby the given start and destination station into account"
// see rest.exe docs
2024-12-08 21:42:57 +00:00
// ushrp: Boolean(opt.startWithWalking),
2024-02-06 22:58:49 +01:00
} ;
2024-12-08 21:42:57 +00:00
if ( journeysRef ) {
2024-12-07 18:29:16 +00:00
query . pagingReference = journeysRef ;
} else {
2024-12-07 16:16:31 +00:00
query . anfrageZeitpunkt = profile . formatTime ( profile , when ) ;
2024-12-07 18:29:16 +00:00
}
2024-12-07 16:16:31 +00:00
query . ankunftSuche = outFrwd ? 'ABFAHRT' : 'ANKUNFT' ;
2024-02-06 22:58:49 +01:00
if ( opt . results !== null ) {
2024-12-07 16:16:31 +00:00
// TODO query.numF = opt.results;
2020-07-19 22:58:12 +02:00
}
2024-12-07 22:46:04 +00:00
const req = profile . transformJourneysQuery ( { profile , opt } , query ) ;
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2024-12-07 16:16:31 +00:00
const journeys = res . verbindungen
2024-02-06 22:58:49 +01:00
. map ( j => profile . parseJourney ( ctx , j ) ) ;
2020-07-20 16:17:31 +02:00
2021-12-29 18:53:50 +01:00
return {
2024-12-07 16:16:31 +00:00
earlierRef : res . verbindungReference ? . earlier || null ,
laterRef : res . verbindungReference ? . later || null ,
2021-12-29 18:53:50 +01:00
journeys ,
2024-12-08 21:42:57 +00:00
realtimeDataUpdatedAt : null , // TODO
2024-02-06 22:58:49 +01:00
} ;
} ;
2017-11-12 20:02:32 +01:00
2021-12-29 18:53:50 +01:00
const refreshJourney = async ( refreshToken , opt = { } ) => {
2018-07-24 17:37:36 +02:00
if ( 'string' !== typeof refreshToken || ! refreshToken ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'refreshToken must be a non-empty string.' ) ;
2018-07-24 17:37:36 +02:00
}
opt = Object . assign ( {
stopovers : false , // return stations on the way?
tickets : false , // return tickets?
2021-01-26 22:10:44 +01:00
polylines : false , // return leg shapes? (not supported by all endpoints)
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2022-10-24 17:30:05 +02:00
remarks : true , // parse & expose hints & warnings?
scheduledDays : false , // parse & expose dates the journey is valid on?
2024-02-06 22:58:49 +01:00
} , opt ) ;
2018-07-24 17:37:36 +02:00
2024-02-06 22:58:49 +01:00
const req = profile . formatRefreshJourneyReq ( { profile , opt } , refreshToken ) ;
2020-02-07 02:31:51 +01:00
2024-02-06 22:58:49 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
const ctx = { profile , opt , common , res } ;
2021-12-29 18:53:50 +01:00
return {
2024-12-11 23:51:58 +00:00
journey : profile . parseJourney ( ctx , res . verbindungen [ 0 ] ) ,
realtimeDataUpdatedAt : null , // TODO
2024-02-06 22:58:49 +01:00
} ;
} ;
2017-11-12 20:02:32 +01:00
2018-10-02 16:36:37 +02:00
// Although the DB Navigator app passes the *first* stopover of the trip
// (instead of the previous one), it seems to work with the previous one as well.
const journeysFromTrip = async ( fromTripId , previousStopover , to , opt = { } ) => {
if ( ! isNonEmptyString ( fromTripId ) ) {
2024-02-06 22:58:49 +01:00
throw new Error ( 'fromTripId must be a non-empty string.' ) ;
2018-10-02 16:36:37 +02:00
}
if ( 'string' === typeof to ) {
2024-02-06 22:58:49 +01:00
to = profile . formatStation ( to ) ;
2018-10-02 16:36:37 +02:00
} else if ( isObj ( to ) && ( to . type === 'station' || to . type === 'stop' ) ) {
2024-02-06 22:58:49 +01:00
to = profile . formatStation ( to . id ) ;
} else {
throw new Error ( 'to must be a valid stop or station.' ) ;
}
2018-10-02 16:36:37 +02:00
2024-02-06 22:58:49 +01:00
if ( ! isObj ( previousStopover ) ) {
throw new Error ( 'previousStopover must be an object.' ) ;
}
2018-10-02 16:36:37 +02:00
2024-02-06 22:58:49 +01:00
let prevStop = previousStopover . stop ;
2018-10-02 16:36:37 +02:00
if ( isObj ( prevStop ) ) {
2024-02-06 22:58:49 +01:00
prevStop = profile . formatStation ( prevStop . id ) ;
2018-10-02 16:36:37 +02:00
} else if ( 'string' === typeof prevStop ) {
2024-02-06 22:58:49 +01:00
prevStop = profile . formatStation ( prevStop ) ;
} else {
throw new Error ( 'previousStopover.stop must be a valid stop or station.' ) ;
}
2018-10-02 16:36:37 +02:00
2024-02-06 22:58:49 +01:00
let depAtPrevStop = previousStopover . departure || previousStopover . plannedDeparture ;
2018-10-02 16:36:37 +02:00
if ( ! isNonEmptyString ( depAtPrevStop ) ) {
2024-02-06 22:58:49 +01:00
throw new Error ( 'previousStopover.(planned)departure must be a string' ) ;
2018-10-02 16:36:37 +02:00
}
2024-02-06 22:58:49 +01:00
depAtPrevStop = Date . parse ( depAtPrevStop ) ;
2018-10-02 16:36:37 +02:00
if ( Number . isNaN ( depAtPrevStop ) ) {
2024-02-06 22:58:49 +01:00
throw new Error ( 'previousStopover.(planned)departure is invalid' ) ;
2018-10-02 16:36:37 +02:00
}
if ( depAtPrevStop > Date . now ( ) ) {
2024-02-06 22:58:49 +01:00
throw new Error ( 'previousStopover.(planned)departure must be in the past' ) ;
2018-10-02 16:36:37 +02:00
}
opt = Object . assign ( {
stopovers : false , // return stations on the way?
transferTime : 0 , // minimum time for a single transfer in minutes
accessibility : 'none' , // 'none', 'partial' or 'complete'
tickets : false , // return tickets?
polylines : false , // return leg shapes?
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
remarks : true , // parse & expose hints & warnings?
2024-02-06 22:58:49 +01:00
} , opt ) ;
2018-10-02 16:36:37 +02:00
// make clear that `departure`/`arrival`/`when` are not supported
2024-02-06 22:58:49 +01:00
if ( opt . departure ) {
throw new Error ( 'journeysFromTrip + opt.departure is not supported by HAFAS.' ) ;
}
if ( opt . arrival ) {
throw new Error ( 'journeysFromTrip + opt.arrival is not supported by HAFAS.' ) ;
}
if ( opt . when ) {
throw new Error ( 'journeysFromTrip + opt.when is not supported by HAFAS.' ) ;
}
2018-10-02 16:36:37 +02:00
const filters = [
2024-02-06 22:58:49 +01:00
profile . formatProductsFilter ( { profile } , opt . products || { } ) ,
] ;
2018-10-02 16:36:37 +02:00
if (
2024-02-06 22:58:49 +01:00
opt . accessibility
&& profile . filters
&& profile . filters . accessibility
&& profile . filters . accessibility [ opt . accessibility ]
2018-10-02 16:36:37 +02:00
) {
2024-02-06 22:58:49 +01:00
filters . push ( profile . filters . accessibility [ opt . accessibility ] ) ;
2018-10-02 16:36:37 +02:00
}
// todo: support walking speed filter
// todo: are these supported?
// - getPT
// - getIV
// - trfReq
// features from `journeys()` not supported here:
// - `maxChg`: maximum nr of transfers
// - `bike`: only bike-friendly journeys
// - `numF`: how many journeys?
// - `via`: let journeys pass this station
// todo: find a way to support them
const query = {
// https://github.com/marudor/BahnhofsAbfahrten/blob/49ebf8b36576547112e61a6273bee770f0769660/packages/types/HAFAS/SearchOnTrip.ts#L16-L30
2021-10-26 14:15:17 +02:00
// todo: support search by `journey.refreshToken` (a.k.a. `ctxRecon`) via `sotMode: RC`?
2018-10-02 16:36:37 +02:00
sotMode : 'JI' , // seach by trip ID (a.k.a. "JID")
jid : fromTripId ,
locData : { // when & where the trip has been entered
loc : prevStop ,
type : 'DEP' , // todo: are there other values?
date : profile . formatDate ( profile , depAtPrevStop ) ,
2024-02-06 22:58:49 +01:00
time : profile . formatTime ( profile , depAtPrevStop ) ,
2018-10-02 16:36:37 +02:00
} ,
arrLocL : [ to ] ,
jnyFltrL : filters ,
2024-02-06 22:58:49 +01:00
getPasslist : Boolean ( opt . stopovers ) ,
getPolyline : Boolean ( opt . polylines ) ,
2018-10-02 16:36:37 +02:00
minChgTime : opt . transferTime ,
2024-02-06 22:58:49 +01:00
getTariff : Boolean ( opt . tickets ) ,
} ;
2018-10-02 16:36:37 +02:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , {
cfg : { polyEnc : 'GPA' } ,
meth : 'SearchOnTrip' ,
req : query ,
2024-02-06 22:58:49 +01:00
} ) ;
if ( ! Array . isArray ( res . outConL ) ) {
return [ ] ;
}
2018-10-02 16:36:37 +02:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2021-12-29 21:14:50 +01:00
const journeys = res . outConL
2024-02-06 22:58:49 +01:00
. map ( rawJourney => profile . parseJourney ( ctx , rawJourney ) )
. map ( ( journey ) => {
2018-10-02 16:36:37 +02:00
// For the first (transit) leg, HAFAS sometimes returns *all* past
// stopovers of the trip, even though it should only return stopovers
// between the specified `depAtPrevStop` and the arrival at the
// interchange station. We slice the leg accordingly.
2024-02-06 22:58:49 +01:00
const fromLegI = journey . legs . findIndex ( l => l . tripId === fromTripId ) ;
if ( fromLegI < 0 ) {
return journey ;
}
const fromLeg = journey . legs [ fromLegI ] ;
return {
... journey ,
legs : [
... journey . legs . slice ( 0 , fromLegI ) ,
sliceLeg ( fromLeg , previousStopover . stop , fromLeg . destination ) ,
... journey . legs . slice ( fromLegI + 2 ) ,
] ,
} ;
} ) ;
2021-12-29 21:14:50 +01:00
return {
journeys ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2018-10-02 16:36:37 +02:00
2021-12-29 18:53:50 +01:00
const locations = async ( query , opt = { } ) => {
2018-03-04 19:53:53 +01:00
if ( ! isNonEmptyString ( query ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'query must be a non-empty string.' ) ;
2018-02-28 16:09:23 +01:00
}
2017-11-12 20:19:33 +01:00
opt = Object . assign ( {
fuzzy : true , // find only exact matches?
2018-12-07 17:00:24 +01:00
results : 5 , // how many search results?
2019-01-23 12:58:05 +08:00
stops : true , // return stops/stations?
2017-11-12 20:19:33 +01:00
addresses : true ,
2018-06-28 13:00:33 +02:00
poi : true , // points of interest
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2024-02-06 22:58:49 +01:00
linesOfStops : false , // parse & expose lines at each stop/station?
} , opt ) ;
const req = profile . formatLocationsReq ( { profile , opt } , query ) ;
2019-10-31 20:08:56 +01:00
2024-12-07 22:46:04 +00:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
2024-12-08 21:42:57 +00:00
2019-10-20 01:47:09 +02:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2024-12-07 18:29:16 +00:00
return res . map ( loc => profile . parseLocation ( ctx , loc ) ) ;
2024-02-06 22:58:49 +01:00
} ;
2017-11-12 20:19:33 +01:00
2021-12-29 18:53:50 +01:00
const stop = async ( stop , opt = { } ) => {
2024-02-06 22:58:49 +01:00
if ( 'object' === typeof stop ) {
stop = profile . formatStation ( stop . id ) ;
} else if ( 'string' === typeof stop ) {
stop = profile . formatStation ( stop ) ;
} else {
throw new TypeError ( 'stop must be an object or a string.' ) ;
}
2018-01-26 17:08:07 +01:00
2018-06-28 13:00:33 +02:00
opt = Object . assign ( {
2020-05-21 17:31:51 +02:00
linesOfStops : false , // parse & expose lines at the stop/station?
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2020-05-21 17:31:51 +02:00
remarks : true , // parse & expose hints & warnings?
2024-02-06 22:58:49 +01:00
} , opt ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const req = profile . formatStopReq ( { profile , opt } , stop ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
2021-12-29 18:53:50 +01:00
if ( ! res || ! Array . isArray ( res . locL ) || ! res . locL [ 0 ] ) {
2022-05-03 23:21:44 +02:00
throw new HafasError ( 'invalid response, expected locL[0]' , null , {
// This problem occurs on invalid input. 🙄
code : INVALID _REQUEST ,
2024-02-06 22:58:49 +01:00
} ) ;
2021-12-29 18:53:50 +01:00
}
2019-10-20 01:47:09 +02:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , res , common } ;
return profile . parseLocation ( ctx , res . locL [ 0 ] ) ;
} ;
2018-01-26 17:08:07 +01:00
2021-12-29 18:53:50 +01:00
const nearby = async ( location , opt = { } ) => {
2024-02-06 22:58:49 +01:00
validateLocation ( location , 'location' ) ;
2018-01-05 14:53:03 +01:00
2017-11-12 20:29:57 +01:00
opt = Object . assign ( {
results : 8 , // maximum number of results
distance : null , // maximum walking distance in meters
poi : false , // return points of interest?
2019-01-23 12:58:05 +08:00
stops : true , // return stops/stations?
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2024-02-06 22:58:49 +01:00
linesOfStops : false , // parse & expose lines at each stop/station?
} , opt ) ;
2017-11-12 20:29:57 +01:00
2024-02-06 22:58:49 +01:00
const req = profile . formatNearbyReq ( { profile , opt } , location ) ;
2024-12-07 22:46:04 +00:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2024-12-17 19:41:00 +00:00
const results = res . map ( loc => {
const res = profile . parseLocation ( ctx , loc ) ;
if ( res . latitude || res . location ? . latitude ) {
res . distance = Math . round ( distance ( location . latitude , location . longitude , res . latitude || res . location ? . latitude , res . longitude || res . location ? . longitude ) * 1000 ) ;
}
return res ;
} ) ;
2021-12-29 18:53:50 +01:00
return Number . isInteger ( opt . results )
? results . slice ( 0 , opt . results )
2024-02-06 22:58:49 +01:00
: results ;
} ;
2017-11-12 20:29:57 +01:00
2021-12-29 21:33:42 +01:00
const trip = async ( id , opt = { } ) => {
2018-06-29 14:58:43 +02:00
if ( ! isNonEmptyString ( id ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'id must be a non-empty string.' ) ;
2018-02-28 16:09:23 +01:00
}
2017-12-17 20:33:04 +01:00
opt = Object . assign ( {
2018-06-13 20:39:33 +02:00
stopovers : true , // return stations on the way?
2018-07-16 11:35:47 +02:00
polyline : false , // return a track shape?
2020-03-18 20:04:39 +01:00
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2022-11-16 15:18:12 +01:00
remarks : true , // parse & expose hints & warnings?
scheduledDays : false , // parse & expose dates trip is valid on?
2024-02-06 22:58:49 +01:00
} , opt ) ;
2017-11-20 15:43:13 +01:00
2024-02-06 22:58:49 +01:00
const req = profile . formatTripReq ( { profile , opt } , id ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
const ctx = { profile , opt , common , res } ;
2021-12-29 18:53:50 +01:00
2024-02-06 22:58:49 +01:00
const trip = profile . parseTrip ( ctx , res . journey ) ;
2021-12-29 21:23:28 +01:00
return {
trip ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2017-11-20 15:43:13 +01:00
2021-10-21 23:00:44 +02:00
// todo [breaking]: rename to trips()?
2021-12-29 18:53:50 +01:00
const tripsByName = async ( lineNameOrFahrtNr = '*' , opt = { } ) => {
2019-12-17 14:12:35 +01:00
if ( ! isNonEmptyString ( lineNameOrFahrtNr ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'lineNameOrFahrtNr must be a non-empty string.' ) ;
2019-12-17 14:12:35 +01:00
}
opt = Object . assign ( {
2021-10-21 23:00:44 +02:00
when : null ,
fromWhen : null , untilWhen : null ,
onlyCurrentlyRunning : true ,
2021-10-22 19:27:29 +02:00
products : { } ,
currentlyStoppingAt : null ,
lineName : null ,
operatorNames : null ,
additionalFilters : [ ] , // undocumented
2024-02-06 22:58:49 +01:00
} , opt ) ;
2021-10-21 23:00:44 +02:00
const req = {
// fields: https://github.com/marudor/BahnhofsAbfahrten/blob/f619e754f212980261eb7e2b151cd73ba0213da8/packages/types/HAFAS/JourneyMatch.ts#L4-L23
input : lineNameOrFahrtNr ,
onlyCR : opt . onlyCurrentlyRunning ,
2021-10-22 19:27:29 +02:00
jnyFltrL : [
profile . formatProductsFilter ( { profile } , opt . products ) ,
] ,
2021-10-21 23:00:44 +02:00
// todo: passing `tripId` yields a `CGI_READ_FAILED` error
// todo: passing a stop ID as `extId` yields a `PARAMETER` error
// todo: `onlyRT: true` reduces the number of results, but filters recent trips 🤔
// todo: `onlyTN: true` yields a `NO_MATCH` error
2021-10-22 19:27:29 +02:00
// todo: useAeqi
2024-02-06 22:58:49 +01:00
} ;
2021-10-21 23:00:44 +02:00
if ( opt . when !== null ) {
2024-02-06 22:58:49 +01:00
req . date = profile . formatDate ( profile , new Date ( opt . when ) ) ;
req . time = profile . formatTime ( profile , new Date ( opt . when ) ) ;
2021-10-21 23:00:44 +02:00
}
// todo: fromWhen doesn't work yet, but untilWhen does
if ( opt . fromWhen !== null ) {
2024-02-06 22:58:49 +01:00
req . dateB = profile . formatDate ( profile , new Date ( opt . fromWhen ) ) ;
req . timeB = profile . formatTime ( profile , new Date ( opt . fromWhen ) ) ;
2021-10-21 23:00:44 +02:00
}
if ( opt . untilWhen !== null ) {
2024-02-06 22:58:49 +01:00
req . dateE = profile . formatDate ( profile , new Date ( opt . untilWhen ) ) ;
req . timeE = profile . formatTime ( profile , new Date ( opt . untilWhen ) ) ;
2021-10-21 23:00:44 +02:00
}
2024-02-06 22:58:49 +01:00
const filter = ( mode , type , value ) => ( { mode , type , value } ) ;
2021-10-22 19:27:29 +02:00
if ( opt . currentlyStoppingAt !== null ) {
if ( ! isNonEmptyString ( opt . currentlyStoppingAt ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'opt.currentlyStoppingAt must be a non-empty string.' ) ;
2021-10-22 19:27:29 +02:00
}
2024-02-06 22:58:49 +01:00
req . jnyFltrL . push ( filter ( 'INC' , 'STATIONS' , opt . currentlyStoppingAt ) ) ;
2021-10-22 19:27:29 +02:00
}
if ( opt . lineName !== null ) {
if ( ! isNonEmptyString ( opt . lineName ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'opt.lineName must be a non-empty string.' ) ;
2021-10-22 19:27:29 +02:00
}
// todo: does this target `line` or `lineId`?
2024-02-06 22:58:49 +01:00
req . jnyFltrL . push ( filter ( 'INC' , 'LINE' , opt . lineName ) ) ;
2021-10-22 19:27:29 +02:00
}
if ( opt . operatorNames !== null ) {
if (
! Array . isArray ( opt . operatorNames )
|| opt . operatorNames . length === 0
|| ! opt . operatorNames . every ( isNonEmptyString )
) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'opt.operatorNames must be an array of non-empty strings.' ) ;
2021-10-22 19:27:29 +02:00
}
// todo: is the an escaping mechanism for ","
2024-02-06 22:58:49 +01:00
req . jnyFltrL . push ( filter ( 'INC' , 'OP' , opt . operatorNames . join ( ',' ) ) ) ;
2021-10-22 19:27:29 +02:00
}
2024-02-06 22:58:49 +01:00
req . jnyFltrL = [ ... req . jnyFltrL , ... opt . additionalFilters ] ;
2019-12-17 14:12:35 +01:00
2021-12-29 18:53:50 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , {
2019-12-17 14:12:35 +01:00
cfg : { polyEnc : 'GPA' } ,
meth : 'JourneyMatch' ,
2021-10-21 23:00:44 +02:00
req ,
2024-02-06 22:58:49 +01:00
} ) ;
2021-10-26 14:15:17 +02:00
// todo [breaking]: catch `NO_MATCH` errors, return []
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2021-12-29 18:53:50 +01:00
2024-02-06 22:58:49 +01:00
const trips = res . jnyL . map ( t => profile . parseTrip ( ctx , t ) ) ;
2021-12-29 21:23:28 +01:00
return {
trips ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2019-12-17 14:12:35 +01:00
2021-12-29 18:53:50 +01:00
const radar = async ( { north , west , south , east } , opt ) => {
2024-02-06 22:58:49 +01:00
if ( 'number' !== typeof north ) {
throw new TypeError ( 'north must be a number.' ) ;
}
if ( 'number' !== typeof west ) {
throw new TypeError ( 'west must be a number.' ) ;
}
if ( 'number' !== typeof south ) {
throw new TypeError ( 'south must be a number.' ) ;
}
if ( 'number' !== typeof east ) {
throw new TypeError ( 'east must be a number.' ) ;
}
2024-02-22 13:25:44 +01:00
// With a bounding box across the antimeridian, east (e.g. -175) might be smaller than west (e.g. 175).
// Likewise, across the north/south poles, north (e.g. -85) might be smaller than south (e.g. 85).
// In these cases, the terms north/south & east/west become rather arbitrary of couse.
// see also https://antimeridian.readthedocs.io/en/stable/
// todo: how does HAFAS handle this?
if ( north === south ) {
throw new Error ( 'bbox.north must not be equal to bbox.south.' ) ;
}
if ( east === west ) {
throw new Error ( 'bbox.east must not be equal to bbox.west.' ) ;
2024-02-06 22:58:49 +01:00
}
2017-11-20 17:37:08 +01:00
opt = Object . assign ( {
results : 256 , // maximum number of vehicles
duration : 30 , // compute frames for the next n seconds
2018-06-13 20:25:56 +02:00
// todo: what happens with `frames: 0`?
2017-12-12 03:28:54 +01:00
frames : 3 , // nr of frames to compute
2018-05-15 19:39:28 +02:00
products : null , // optionally an object of booleans
2020-03-18 20:04:39 +01:00
polylines : true , // return a track shape for each vehicle?
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2024-02-06 22:58:49 +01:00
} , opt || { } ) ;
opt . when = new Date ( opt . when || Date . now ( ) ) ;
if ( Number . isNaN ( Number ( opt . when ) ) ) {
throw new TypeError ( 'opt.when is invalid' ) ;
}
2017-11-20 17:37:08 +01:00
2024-02-06 22:58:49 +01:00
const req = profile . formatRadarReq ( { profile , opt } , north , west , south , east ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
if ( ! Array . isArray ( res . jnyL ) ) {
return [ ] ;
}
const ctx = { profile , opt , common , res } ;
2017-11-20 17:37:08 +01:00
2024-02-06 22:58:49 +01:00
const movements = res . jnyL . map ( m => profile . parseMovement ( ctx , m ) ) ;
2021-12-29 21:24:07 +01:00
return {
movements ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2017-11-20 17:37:08 +01:00
2021-12-29 18:51:34 +01:00
const reachableFrom = async ( address , opt = { } ) => {
2024-02-06 22:58:49 +01:00
validateLocation ( address , 'address' ) ;
2018-08-25 01:52:46 +02:00
opt = Object . assign ( {
when : Date . now ( ) ,
maxTransfers : 5 , // maximum of 5 transfers
2018-11-01 19:38:29 +01:00
maxDuration : 20 , // maximum travel duration in minutes, pass `null` for infinite
2020-03-18 20:04:39 +01:00
products : { } ,
subStops : true , // parse & expose sub-stops of stations?
entrances : true , // parse & expose entrances of stops/stations?
2021-01-26 22:10:44 +01:00
polylines : false , // return leg shapes?
2024-02-06 22:58:49 +01:00
} , opt ) ;
if ( Number . isNaN ( Number ( opt . when ) ) ) {
throw new TypeError ( 'opt.when is invalid' ) ;
}
2018-08-25 01:52:46 +02:00
2024-02-06 22:58:49 +01:00
const req = profile . formatReachableFromReq ( { profile , opt } , address ) ;
2019-10-31 20:08:56 +01:00
2024-02-06 22:58:49 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , req ) ;
2021-12-29 18:51:34 +01:00
if ( ! Array . isArray ( res . posL ) ) {
2022-05-03 23:21:44 +02:00
throw new HafasError ( 'invalid response, expected posL[0]' , null , {
shouldRetry : true ,
2024-02-06 22:58:49 +01:00
} ) ;
2021-12-29 18:51:34 +01:00
}
2024-02-06 22:58:49 +01:00
const byDuration = [ ] ;
let i = 0 , lastDuration = NaN ;
2021-12-29 18:51:34 +01:00
for ( const pos of sortBy ( res . posL , 'dur' ) ) {
2024-02-06 22:58:49 +01:00
const loc = common . locations [ pos . locX ] ;
if ( ! loc ) {
continue ;
}
2021-12-29 18:51:34 +01:00
if ( pos . dur !== lastDuration ) {
2024-02-06 22:58:49 +01:00
lastDuration = pos . dur ;
i = byDuration . length ;
2021-12-29 18:51:34 +01:00
byDuration . push ( {
duration : pos . dur ,
2024-02-06 22:58:49 +01:00
stations : [ loc ] ,
} ) ;
2021-12-29 18:51:34 +01:00
} else {
2024-02-06 22:58:49 +01:00
byDuration [ i ] . stations . push ( loc ) ;
2021-12-29 18:51:34 +01:00
}
2018-09-03 20:38:56 +02:00
}
2021-12-29 21:25:14 +01:00
return {
reachable : byDuration ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2018-08-25 01:52:46 +02:00
2020-03-09 20:54:43 +01:00
const remarks = async ( opt = { } ) => {
opt = {
results : 100 , // maximum number of remarks
// filter by time
from : Date . now ( ) ,
to : null ,
products : null , // filter by affected products
2021-01-26 22:10:44 +01:00
polylines : false , // return leg shapes? (not supported by all endpoints)
2024-02-06 22:58:49 +01:00
... opt ,
} ;
2020-03-09 20:54:43 +01:00
if ( opt . from !== null ) {
2024-02-06 22:58:49 +01:00
opt . from = new Date ( opt . from ) ;
validateWhen ( opt . from , 'opt.from' ) ;
2020-03-09 20:54:43 +01:00
}
if ( opt . to !== null ) {
2024-02-06 22:58:49 +01:00
opt . to = new Date ( opt . to ) ;
validateWhen ( opt . to , 'opt.to' ) ;
2020-03-09 20:54:43 +01:00
}
2024-02-06 22:58:49 +01:00
const req = profile . formatRemarksReq ( { profile , opt } ) ;
2020-03-09 20:54:43 +01:00
const {
res , common ,
2024-02-06 22:58:49 +01:00
} = await profile . request ( { profile , opt } , userAgent , req ) ;
2020-03-09 20:54:43 +01:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2021-12-29 21:25:49 +01:00
const remarks = ( res . msgL || [ ] )
2024-02-06 22:58:49 +01:00
. map ( w => profile . parseWarning ( ctx , w ) ) ;
2021-12-29 21:25:49 +01:00
return {
remarks ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2020-03-09 20:54:43 +01:00
2020-03-09 21:44:35 +01:00
const lines = async ( query , opt = { } ) => {
if ( ! isNonEmptyString ( query ) ) {
2024-02-06 22:58:49 +01:00
throw new TypeError ( 'query must be a non-empty string.' ) ;
2020-03-09 21:44:35 +01:00
}
2024-02-06 22:58:49 +01:00
const req = profile . formatLinesReq ( { profile , opt } , query ) ;
2020-03-09 21:44:35 +01:00
const {
res , common ,
2024-02-06 22:58:49 +01:00
} = await profile . request ( { profile , opt } , userAgent , req ) ;
2020-03-09 21:44:35 +01:00
2024-02-06 22:58:49 +01:00
if ( ! Array . isArray ( res . lineL ) ) {
return [ ] ;
}
const ctx = { profile , opt , common , res } ;
2021-12-29 21:26:12 +01:00
const lines = res . lineL . map ( l => {
2024-02-06 22:58:49 +01:00
const parseDirRef = i => ( res . common . dirL [ i ] || { } ) . txt || null ;
2020-03-09 21:44:35 +01:00
return {
... omit ( l . line , [ 'id' , 'fahrtNr' ] ) ,
id : l . lineId ,
// todo: what is locX?
directions : Array . isArray ( l . dirRefL )
? l . dirRefL . map ( parseDirRef )
: null ,
trips : Array . isArray ( l . jnyL )
? l . jnyL . map ( t => profile . parseTrip ( ctx , t ) )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ) ;
2021-12-29 21:26:12 +01:00
return {
lines ,
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2020-03-09 21:44:35 +01:00
2020-03-09 19:47:29 +01:00
const serverInfo = async ( opt = { } ) => {
2022-02-22 20:23:54 +01:00
opt = {
versionInfo : true , // query HAFAS versions?
2024-02-06 22:58:49 +01:00
... opt ,
} ;
2022-02-22 20:23:54 +01:00
2020-03-09 19:47:29 +01:00
const { res , common } = await profile . request ( { profile , opt } , userAgent , {
meth : 'ServerInfo' ,
2022-02-22 20:23:54 +01:00
req : {
getVersionInfo : opt . versionInfo ,
} ,
2024-02-06 22:58:49 +01:00
} ) ;
2020-03-09 19:47:29 +01:00
2024-02-06 22:58:49 +01:00
const ctx = { profile , opt , common , res } ;
2020-03-09 19:47:29 +01:00
return {
2022-02-22 20:23:54 +01:00
// todo: what are .serverVersion & .clientVersion?
hciVersion : res . hciVersion || null ,
2020-03-09 19:47:29 +01:00
timetableStart : res . fpB || null ,
timetableEnd : res . fpE || null ,
serverTime : res . sD && res . sT
? profile . parseDateTime ( ctx , res . sD , res . sT )
: null ,
2022-06-18 18:19:04 +02:00
realtimeDataUpdatedAt : res . planrtTS && res . planrtTS !== '0'
2020-03-09 19:47:29 +01:00
? parseInt ( res . planrtTS )
: null ,
2024-02-06 22:58:49 +01:00
} ;
} ;
2020-03-09 19:47:29 +01:00
const client = {
departures ,
arrivals ,
journeys ,
locations ,
stop ,
nearby ,
serverInfo ,
2024-02-06 22:58:49 +01:00
} ;
if ( profile . trip ) {
client . trip = trip ;
}
if ( profile . radar ) {
client . radar = radar ;
}
if ( profile . refreshJourney ) {
client . refreshJourney = refreshJourney ;
}
if ( profile . journeysFromTrip ) {
client . journeysFromTrip = journeysFromTrip ;
}
if ( profile . reachableFrom ) {
client . reachableFrom = reachableFrom ;
}
if ( profile . tripsByName ) {
client . tripsByName = tripsByName ;
}
if ( profile . remarks !== false ) {
client . remarks = remarks ;
}
if ( profile . lines !== false ) {
client . lines = lines ;
2020-03-09 19:47:29 +01:00
}
2024-02-06 22:58:49 +01:00
Object . defineProperty ( client , 'profile' , { value : profile } ) ;
return client ;
} ;
2016-06-22 01:39:59 +02:00
2022-05-07 16:17:37 +02:00
export {
createClient ,
2024-02-06 22:58:49 +01:00
} ;