mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-23 07:09:35 +02:00
merge new-vbb-protocol into master
This commit is contained in:
commit
95833edd21
10 changed files with 128 additions and 64 deletions
|
@ -7,7 +7,7 @@ const formatLocationIdentifier = (data) => {
|
||||||
for (let key in data) {
|
for (let key in data) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(data, key)) continue
|
if (!Object.prototype.hasOwnProperty.call(data, key)) continue
|
||||||
|
|
||||||
str += key + '=' + data[key] + sep // todo: escape
|
str += key + '=' + data[key] + sep // todo: escape, but how?
|
||||||
}
|
}
|
||||||
|
|
||||||
return str
|
return str
|
||||||
|
|
|
@ -13,9 +13,9 @@ const formatPoi = (p) => {
|
||||||
lid: formatLocationIdentifier({
|
lid: formatLocationIdentifier({
|
||||||
A: '4', // POI
|
A: '4', // POI
|
||||||
O: p.name,
|
O: p.name,
|
||||||
|
L: p.id,
|
||||||
X: formatCoord(p.longitude),
|
X: formatCoord(p.longitude),
|
||||||
Y: formatCoord(p.latitude),
|
Y: formatCoord(p.latitude)
|
||||||
L: p.id
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const formatStation = id => ({type: 'S', lid: 'L=' + id})
|
const formatLocationIdentifier = require('./location-identifier')
|
||||||
|
|
||||||
|
const formatStation = (id) => {
|
||||||
|
return {
|
||||||
|
// todo: name necessary?
|
||||||
|
lid: formatLocationIdentifier({
|
||||||
|
A: '1', // station
|
||||||
|
L: id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = formatStation
|
module.exports = formatStation
|
||||||
|
|
87
index.js
87
index.js
|
@ -101,41 +101,64 @@ const createClient = (profile, request = _request) => {
|
||||||
filters.push(profile.filters.accessibility[opt.accessibility])
|
filters.push(profile.filters.accessibility[opt.accessibility])
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = profile.transformJourneysQuery({
|
// With protocol version `1.16`, the VBB endpoint fails with
|
||||||
outDate: profile.formatDate(profile, opt.when),
|
// `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
|
||||||
outTime: profile.formatTime(profile, opt.when),
|
// of results. To circumvent this, we loop here, collecting journeys
|
||||||
ctxScr: journeysRef,
|
// until we have enough.
|
||||||
numF: opt.results,
|
// see https://github.com/derhuerst/hafas-client/pull/23#issuecomment-370246163
|
||||||
getPasslist: !!opt.passedStations,
|
// todo: check if `numF` is supported again, revert this change
|
||||||
maxChg: opt.transfers,
|
const journeys = []
|
||||||
minChgTime: opt.transferTime,
|
const more = (when, journeysRef) => {
|
||||||
depLocL: [from],
|
const query = {
|
||||||
viaLocL: opt.via ? [{loc: opt.via}] : null,
|
outDate: profile.formatDate(profile, when),
|
||||||
arrLocL: [to],
|
outTime: profile.formatTime(profile, when),
|
||||||
jnyFltrL: filters,
|
ctxScr: journeysRef,
|
||||||
getTariff: !!opt.tickets,
|
// numF: opt.results,
|
||||||
|
getPasslist: !!opt.passedStations,
|
||||||
|
maxChg: opt.transfers,
|
||||||
|
minChgTime: opt.transferTime,
|
||||||
|
depLocL: [from],
|
||||||
|
viaLocL: opt.via ? [{loc: opt.via}] : null,
|
||||||
|
arrLocL: [to],
|
||||||
|
jnyFltrL: filters,
|
||||||
|
getTariff: !!opt.tickets,
|
||||||
|
|
||||||
// todo: what is req.gisFltrL?
|
// todo: what is req.gisFltrL?
|
||||||
getPT: true, // todo: what is this?
|
getPT: true, // todo: what is this?
|
||||||
outFrwd: true, // todo: what is this?
|
outFrwd: true, // todo: what is this?
|
||||||
getIV: false, // todo: walk & bike as alternatives?
|
getIV: false, // todo: walk & bike as alternatives?
|
||||||
getPolyline: false // todo: shape for displaying on a map?
|
getPolyline: false // todo: shape for displaying on a map?
|
||||||
}, opt)
|
}
|
||||||
|
|
||||||
return request(profile, {
|
return request(profile, {
|
||||||
cfg: {polyEnc: 'GPA'},
|
cfg: {polyEnc: 'GPA'},
|
||||||
meth: 'TripSearch',
|
meth: 'TripSearch',
|
||||||
req: query
|
req: profile.transformJourneysQuery(query, opt)
|
||||||
})
|
})
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
if (!Array.isArray(d.outConL)) return []
|
if (!Array.isArray(d.outConL)) return []
|
||||||
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks)
|
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks)
|
||||||
const res = d.outConL.map(parse)
|
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
|
||||||
|
|
||||||
if (d.outCtxScrB) res.earlierRef = d.outCtxScrB
|
let latestDep = -Infinity
|
||||||
if (d.outCtxScrF) res.laterRef = d.outCtxScrF
|
for (let j of d.outConL) {
|
||||||
return res
|
j = parse(j)
|
||||||
})
|
journeys.push(j)
|
||||||
|
|
||||||
|
if (journeys.length === opt.results) { // collected enough
|
||||||
|
journeys.laterRef = d.outCtxScrF
|
||||||
|
return journeys
|
||||||
|
}
|
||||||
|
const dep = +new Date(j.departure)
|
||||||
|
if (dep > latestDep) latestDep = dep
|
||||||
|
}
|
||||||
|
|
||||||
|
const when = new Date(latestDep)
|
||||||
|
return more(when, d.outCtxScrF) // otherwise continue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return more(opt.when, journeysRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
const locations = (query, opt = {}) => {
|
const locations = (query, opt = {}) => {
|
||||||
|
|
|
@ -26,6 +26,10 @@ const filters = require('../format/filters')
|
||||||
const id = x => x
|
const id = x => x
|
||||||
|
|
||||||
const defaultProfile = {
|
const defaultProfile = {
|
||||||
|
salt: null,
|
||||||
|
addChecksum: false,
|
||||||
|
addMicMac: false,
|
||||||
|
|
||||||
transformReqBody: id,
|
transformReqBody: id,
|
||||||
transformReq: id,
|
transformReq: id,
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const crypto = require('crypto')
|
||||||
let captureStackTrace = () => {}
|
let captureStackTrace = () => {}
|
||||||
if (process.env.NODE_ENV === 'dev') {
|
if (process.env.NODE_ENV === 'dev') {
|
||||||
captureStackTrace = require('capture-stack-trace')
|
captureStackTrace = require('capture-stack-trace')
|
||||||
|
@ -8,6 +9,8 @@ const {stringify} = require('query-string')
|
||||||
const Promise = require('pinkie-promise')
|
const Promise = require('pinkie-promise')
|
||||||
const {fetch} = require('fetch-ponyfill')({Promise})
|
const {fetch} = require('fetch-ponyfill')({Promise})
|
||||||
|
|
||||||
|
const md5 = input => crypto.createHash('md5').update(input).digest()
|
||||||
|
|
||||||
const request = (profile, data) => {
|
const request = (profile, data) => {
|
||||||
const body = profile.transformReqBody({lang: 'en', svcReqL: [data]})
|
const body = profile.transformReqBody({lang: 'en', svcReqL: [data]})
|
||||||
const req = profile.transformReq({
|
const req = profile.transformReq({
|
||||||
|
@ -19,14 +22,37 @@ const request = (profile, data) => {
|
||||||
'Accept-Encoding': 'gzip, deflate',
|
'Accept-Encoding': 'gzip, deflate',
|
||||||
'user-agent': 'https://github.com/derhuerst/hafas-client'
|
'user-agent': 'https://github.com/derhuerst/hafas-client'
|
||||||
},
|
},
|
||||||
query: null
|
query: {}
|
||||||
})
|
})
|
||||||
const url = profile.endpoint + (req.query ? '?' + stringify(req.query) : '')
|
|
||||||
|
if (profile.addChecksum || profile.addMicMac) {
|
||||||
|
if (!Buffer.isBuffer(profile.salt)) {
|
||||||
|
throw new Error('profile.salt must be a Buffer.')
|
||||||
|
}
|
||||||
|
if (profile.addChecksum) {
|
||||||
|
const checksum = md5(Buffer.concat([
|
||||||
|
Buffer.from(req.body, 'utf8'),
|
||||||
|
profile.salt
|
||||||
|
]))
|
||||||
|
req.query.checksum = checksum.toString('hex')
|
||||||
|
}
|
||||||
|
if (profile.addMicMac) {
|
||||||
|
const mic = md5(Buffer.from(req.body, 'utf8'))
|
||||||
|
req.query.mic = mic.toString('hex')
|
||||||
|
|
||||||
|
const micAsHex = Buffer.from(mic.toString('hex'), 'utf8')
|
||||||
|
const mac = md5(Buffer.concat([micAsHex, profile.salt]))
|
||||||
|
req.query.mac = mac.toString('hex')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = profile.endpoint + '?' + stringify(req.query)
|
||||||
|
|
||||||
// Async stack traces are not supported everywhere yet, so we create our own.
|
// Async stack traces are not supported everywhere yet, so we create our own.
|
||||||
const err = new Error()
|
const err = new Error()
|
||||||
err.isHafasError = true
|
err.isHafasError = true
|
||||||
err.request = body
|
err.request = body
|
||||||
|
err.url = url
|
||||||
captureStackTrace(err)
|
captureStackTrace(err)
|
||||||
|
|
||||||
return fetch(url, req)
|
return fetch(url, req)
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
const _createParseLine = require('../../parse/line')
|
const _createParseLine = require('../../parse/line')
|
||||||
const _createParseJourney = require('../../parse/journey')
|
const _createParseJourney = require('../../parse/journey')
|
||||||
const _formatStation = require('../../format/station')
|
const _formatStation = require('../../format/station')
|
||||||
|
@ -23,17 +21,6 @@ const transformReqBody = (body) => {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
const salt = 'bdI8UVj40K5fvxwf'
|
|
||||||
const transformReq = (req) => {
|
|
||||||
const hash = crypto.createHash('md5')
|
|
||||||
hash.update(req.body + salt)
|
|
||||||
|
|
||||||
if (!req.query) req.query = {}
|
|
||||||
req.query.checksum = hash.digest('hex')
|
|
||||||
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformJourneysQuery = (query, opt) => {
|
const transformJourneysQuery = (query, opt) => {
|
||||||
const filters = query.jnyFltrL
|
const filters = query.jnyFltrL
|
||||||
if (opt.bike) filters.push(bike)
|
if (opt.bike) filters.push(bike)
|
||||||
|
@ -142,8 +129,11 @@ const dbProfile = {
|
||||||
locale: 'de-DE',
|
locale: 'de-DE',
|
||||||
timezone: 'Europe/Berlin',
|
timezone: 'Europe/Berlin',
|
||||||
endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe',
|
endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe',
|
||||||
|
|
||||||
|
salt: Buffer.from('bdI8UVj40K5fvxwf', 'utf8'),
|
||||||
|
addChecksum: true,
|
||||||
|
|
||||||
transformReqBody,
|
transformReqBody,
|
||||||
transformReq,
|
|
||||||
transformJourneysQuery,
|
transformJourneysQuery,
|
||||||
|
|
||||||
products: modes.allProducts,
|
products: modes.allProducts,
|
||||||
|
|
|
@ -20,10 +20,10 @@ const modes = require('./modes')
|
||||||
const formatBitmask = createFormatBitmask(modes)
|
const formatBitmask = createFormatBitmask(modes)
|
||||||
|
|
||||||
const transformReqBody = (body) => {
|
const transformReqBody = (body) => {
|
||||||
body.client = {type: 'IPA', id: 'BVG', name: 'FahrInfo', v: '4070700'}
|
body.client = {type: 'IPA', id: 'VBB', name: 'vbbPROD', v: '4010300'}
|
||||||
body.ext = 'BVG.1'
|
body.ext = 'VBB.1'
|
||||||
body.ver = '1.15' // todo: 1.16 with `mic` and `mac` query params
|
body.ver = '1.16'
|
||||||
body.auth = {type: 'AID', aid: '1Rxs112shyHLatUX4fofnmdxK'}
|
body.auth = {type: 'AID', aid: 'hafas-vbb-apps'}
|
||||||
|
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,13 @@ const formatProducts = (products) => {
|
||||||
const vbbProfile = {
|
const vbbProfile = {
|
||||||
locale: 'de-DE',
|
locale: 'de-DE',
|
||||||
timezone: 'Europe/Berlin',
|
timezone: 'Europe/Berlin',
|
||||||
endpoint: 'https://bvg-apps.hafas.de/bin/mgate.exe',
|
endpoint: 'https://fahrinfo.vbb.de/bin/mgate.exe',
|
||||||
|
|
||||||
|
// https://gist.github.com/derhuerst/a8d94a433358abc015ff77df4481070c#file-haf_config_base-properties-L39
|
||||||
|
// https://runkit.com/derhuerst/hafas-decrypt-encrypted-mac-salt
|
||||||
|
salt: Buffer.from('5243544a4d3266467846667878516649', 'hex'),
|
||||||
|
addMicMac: true,
|
||||||
|
|
||||||
transformReqBody,
|
transformReqBody,
|
||||||
|
|
||||||
products: modes.allProducts,
|
products: modes.allProducts,
|
||||||
|
|
|
@ -175,6 +175,7 @@ The returned [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript
|
||||||
- [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas#vbb-hafas) – JavaScript client for Berlin & Brandenburg public transport HAFAS API.
|
- [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas#vbb-hafas) – JavaScript client for Berlin & Brandenburg public transport HAFAS API.
|
||||||
- [`hafas-departures-in-direction`](https://github.com/derhuerst/hafas-departures-in-direction#hafas-departures-in-direction) – Pass in a HAFAS client, get departures in a certain direction.
|
- [`hafas-departures-in-direction`](https://github.com/derhuerst/hafas-departures-in-direction#hafas-departures-in-direction) – Pass in a HAFAS client, get departures in a certain direction.
|
||||||
- [`hafas-collect-departures-at`](https://github.com/derhuerst/hafas-collect-departures-at#hafas-collect-departures-at) – Utility to collect departures, using any HAFAS client.
|
- [`hafas-collect-departures-at`](https://github.com/derhuerst/hafas-collect-departures-at#hafas-collect-departures-at) – Utility to collect departures, using any HAFAS client.
|
||||||
|
- [`hafas-discover-stations`](https://github.com/derhuerst/hafas-discover-stations#hafas-discover-stations) – Pass in a HAFAS client, discover stations by querying departures.
|
||||||
- [`hafas-rest-api`](https://github.com/derhuerst/hafas-rest-api#hafas-rest-api) – Expose a HAFAS client via an HTTP REST API.
|
- [`hafas-rest-api`](https://github.com/derhuerst/hafas-rest-api#hafas-rest-api) – Expose a HAFAS client via an HTTP REST API.
|
||||||
- [List of european long-distance transport operators, available API endpoints, GTFS feeds and client modules.](https://github.com/public-transport/european-transport-operators)
|
- [List of european long-distance transport operators, available API endpoints, GTFS feeds and client modules.](https://github.com/public-transport/european-transport-operators)
|
||||||
- [Collection of european transport JavaScript modules.](https://github.com/public-transport/european-transport-modules)
|
- [Collection of european transport JavaScript modules.](https://github.com/public-transport/european-transport-modules)
|
||||||
|
|
18
test/vbb.js
18
test/vbb.js
|
@ -246,8 +246,9 @@ test('journey leg details', co(function* (t) {
|
||||||
|
|
||||||
test('journeys – station to address', co(function* (t) {
|
test('journeys – station to address', co(function* (t) {
|
||||||
const journeys = yield client.journeys(spichernstr, {
|
const journeys = yield client.journeys(spichernstr, {
|
||||||
type: 'location', address: 'Torfstraße 17',
|
type: 'location',
|
||||||
latitude: 52.5416823, longitude: 13.3491223
|
address: 'Torfstr. 17, Berlin',
|
||||||
|
latitude: 52.541797, longitude: 13.350042
|
||||||
}, {results: 1, when})
|
}, {results: 1, when})
|
||||||
|
|
||||||
t.ok(Array.isArray(journeys))
|
t.ok(Array.isArray(journeys))
|
||||||
|
@ -261,9 +262,9 @@ test('journeys – station to address', co(function* (t) {
|
||||||
|
|
||||||
const dest = leg.destination
|
const dest = leg.destination
|
||||||
assertValidAddress(t, dest)
|
assertValidAddress(t, dest)
|
||||||
t.strictEqual(dest.address, 'Torfstraße 17')
|
t.strictEqual(dest.address, '13353 Berlin-Wedding, Torfstr. 17')
|
||||||
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.5416823))
|
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.541797))
|
||||||
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.3491223))
|
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.350042))
|
||||||
assertValidWhen(t, leg.arrival, when)
|
assertValidWhen(t, leg.arrival, when)
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -273,7 +274,9 @@ test('journeys – station to address', co(function* (t) {
|
||||||
|
|
||||||
test('journeys – station to POI', co(function* (t) {
|
test('journeys – station to POI', co(function* (t) {
|
||||||
const journeys = yield client.journeys(spichernstr, {
|
const journeys = yield client.journeys(spichernstr, {
|
||||||
type: 'location', id: '9980720', name: 'ATZE Musiktheater',
|
type: 'location',
|
||||||
|
id: '900980720',
|
||||||
|
name: 'Berlin, Atze Musiktheater für Kinder',
|
||||||
latitude: 52.543333, longitude: 13.351686
|
latitude: 52.543333, longitude: 13.351686
|
||||||
}, {results: 1, when})
|
}, {results: 1, when})
|
||||||
|
|
||||||
|
@ -288,7 +291,8 @@ test('journeys – station to POI', co(function* (t) {
|
||||||
|
|
||||||
const dest = leg.destination
|
const dest = leg.destination
|
||||||
assertValidPoi(t, dest)
|
assertValidPoi(t, dest)
|
||||||
t.strictEqual(dest.name, 'ATZE Musiktheater')
|
t.strictEqual(dest.id, '900980720')
|
||||||
|
t.strictEqual(dest.name, 'Berlin, Atze Musiktheater für Kinder')
|
||||||
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.543333))
|
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.543333))
|
||||||
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.351686))
|
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.351686))
|
||||||
assertValidWhen(t, leg.arrival, when)
|
assertValidWhen(t, leg.arrival, when)
|
||||||
|
|
Loading…
Add table
Reference in a new issue