-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
170 lines (150 loc) · 5.53 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
'use strict'
const defaultProfile = require('./lib/default-profile')
const request = require('./lib/request')
const isNonEmptyString = str => typeof str === 'string' && str.length > 0
const createRestClient = (profile, token, userAgent) => {
profile = Object.assign({}, defaultProfile, profile)
profile = {
...defaultProfile,
...profile,
}
if (!isNonEmptyString(profile.endpoint)) throw new Error('missing profile.endpoint')
if (!isNonEmptyString(token)) throw new Error('missing token')
if (!isNonEmptyString(userAgent)) throw new Error('missing userAgent')
const journeys = async (origin, destination, opt = {}) => {
if (('earlierThan' in opt) && ('laterThan' in opt)) {
throw new TypeError('opt.earlierThan and opt.laterThan are mutually exclusive.')
}
if (('departure' in opt) && ('arrival' in opt)) {
throw new TypeError('opt.departure and opt.arrival are mutually exclusive.')
}
let journeysRef = null
if ('earlierThan' in opt) {
if (!isNonEmptyString(opt.earlierThan)) {
throw new TypeError('opt.earlierThan must be a non-empty string.')
}
if (('departure' in opt) || ('arrival' in opt)) {
throw new TypeError('opt.earlierThan and opt.departure/opt.arrival are mutually exclusive.')
}
journeysRef = opt.earlierThan
}
if ('laterThan' in opt) {
if (!isNonEmptyString(opt.laterThan)) {
throw new TypeError('opt.laterThan must be a non-empty string.')
}
if (('departure' in opt) || ('arrival' in opt)) {
throw new TypeError('opt.laterThan and opt.departure/opt.arrival are mutually exclusive.')
}
journeysRef = opt.laterThan
}
opt = {
results: null, // number of journeys – `null` means "whatever HAFAS returns"
stopovers: false, // return stations on the way?
transfers: -1, // maximum of 5 transfers
transferTime: null, // minimum time for a single transfer in minutes
polylines: false, // return leg shapes?
remarks: true, // parse & expose hints & warnings?
// Consider walking to nearby stations at the beginning of a journey?
startWithWalking: true,
// scheduledDays: false
...opt,
}
const query = {
// todo: via, viaWaitTime, avoid, changeTimePercent, addChangeTime,
// products (!), context (!), originWalk, -Bike, bikeCarriage, sleepingCar,
// couchetteCoach, includeEarlier (!)
originId: profile.formatLocation(profile, origin, 'origin').lid,
destId: profile.formatLocation(profile, destination, 'destination').lid,
poly: opt.polylines ? 1 : 0,
passlist: opt.stopovers ? 1 : 0,
showPassingPoints: 0, // return pass-by stopovers
products: profile.formatProductsFilter({ profile }, opt.products || {}).value,
eco: 0,
originWalk: opt.startWithWalking ? 1 : 0,
destWalk: 1,
rtMode: 'FULL', // todo: make customisable?, see https://pastebin.com/qZ9WS3Cx
// "take additional stations nearby the given start and destination station into account"
unsharp: 1,
}
if (opt.transferTime !== null) query.minChangeTime = opt.transferTime
if (opt.transfers >= 0) query.maxChange = opt.transfers
if (opt.results !== null) query.numF = opt.results // todo: what about numB?
let when
if (opt.departure !== undefined && opt.departure !== null) {
when = new Date(opt.departure)
if (Number.isNaN(+when)) throw new TypeError('opt.departure is invalid')
} else if (opt.arrival !== undefined && opt.arrival !== null) {
when = new Date(opt.arrival)
if (Number.isNaN(+when)) throw new TypeError('opt.arrival is invalid')
query.searchForArrival = 1
}
if (when) {
query.date = profile.formatDate({ profile, opt }, when)
query.time = profile.formatTime({ profile, opt }, when)
}
if (journeysRef) {
query.context = journeysRef
}
const ctx = await request({ profile, opt }, token, userAgent, 'trip', query)
// todo: ctx.res.planRtTs
return {
journeys: ctx.res.Trip.map(t => profile.parseJourney(ctx, t)),
earlierRef: ctx.res.scrB,
laterRef: ctx.res.scrF,
}
}
const refreshJourney = async (refreshToken, opt = {}) => {
if (typeof refreshToken !== 'string' || !refreshToken) {
throw new TypeError('refreshToken must be a non-empty string.')
}
opt = {
stopovers: false, // return stations on the way?
polylines: false, // return leg shapes?
remarks: true, // parse & expose hints & warnings?
...opt,
}
const query = {
ctx: refreshToken,
poly: opt.polylines ? 1 : 0,
passlist: opt.stopovers ? 1 : 0,
showPassingPoints: 0, // return pass-by stopovers
eco: 0,
rtMode: 'FULL', // todo: is this required here?
}
const ctx = await request({ profile, opt }, token, userAgent, 'recon', query)
// todo: ctx.res.planRtTs
return ctx.res.Trip[0] ? profile.parseJourney(ctx, ctx.res.Trip[0]) : null
}
const trip = async (tripId, opt = {}) => {
if (typeof tripId !== 'string' || !tripId) {
throw new TypeError('tripId must be a non-empty string.')
}
opt = {
polylines: false, // return leg shapes?
...opt,
}
const query = {
id: tripId,
// @todo date
poly: opt.polylines ? 1 : 0,
showPassingPoints: 0, // return pass-by stopovers
rtMode: 'FULL', // todo: is this required here?
}
const ctx = await request({ profile, opt }, token, userAgent, 'journeyDetails', query)
// todo: ctx.res.planRtTs
return profile.parseTrip(ctx, ctx.res)
}
const dataInfo = async () => {
const ctx = await request({ profile, opt: {} }, token, userAgent, 'datainfo')
return ctx.res
}
const client = {
journeys,
refreshJourney,
trip,
dataInfo,
}
Object.defineProperty(client, 'profile', { value: profile })
return client
}
module.exports = createRestClient