merge next into tests-rewrite

This commit is contained in:
Jannis R 2018-05-13 00:05:10 +02:00
commit 5cea7e47e4
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
15 changed files with 109 additions and 40 deletions

View file

@ -1,6 +1,39 @@
# Changelog # Changelog
## `2.6.0`
- 5d10d76 journey legs: parse cycle
## `2.5.3`
- d676b84 fix parsing for journey leg alternatives 🐛
## `2.5.2`
- 16e6dd6 departure docs: fix method 📝
- c60213a DB: tram mode should be `train` 🐛
## `2.5.1`
- afc0124 fix stopover parsing 🐛
## `2.5.0` ## `2.5.0`
- new [Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) [profile](../p/nahsh) - new [Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) [profile](../p/nahsh)
- new [*writing a profile* guide](./writing-a-profile.md) - new [*writing a profile* guide](./writing-a-profile.md)
## `2.4.2`
- `parseStopover`: expose canceled arrivals & departures 🐛
## `2.4.1`
- new [*writing a profile* guide](./writing-a-profile.md)
- `parseMovement`: use `parseStopover` 🐛
- `parseStopover`: use `parseStationName` 🐛
## `2.4.0`
- new [Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt)/[INSA](https://insa.de) profile
- new `earlierRef`/`laterRef` feature to query earlier/later journeys (pagination)
- former scheduled date & time for canceled departures & journeys

View file

@ -44,7 +44,7 @@ const vbbProfile = require('hafas-client/p/vbb')
const client = createClient(vbbProfile) const client = createClient(vbbProfile)
// S Charlottenburg // S Charlottenburg
client.journeys('900000024101', {duration: 3}) client.departures('900000024101', {duration: 3})
.then(console.log) .then(console.log)
.catch(console.error) .catch(console.error)
``` ```

View file

@ -25,7 +25,8 @@ With `opt`, you can override the default options, which look like this:
```js ```js
{ {
when: new Date(), when: new Date(),
passedStations: true // return stations on the way? passedStations: true, // return stations on the way?
polyline: false // return a shape for the leg?
} }
``` ```
@ -116,3 +117,5 @@ The response looked like this:
passed: [ /* … */ ] passed: [ /* … */ ]
} }
``` ```
If you pass `polyline: true`, the leg will have a `polyline` field, containing an encoded shape. You can use e.g. [`@mapbox/polyline`](https://www.npmjs.com/package/@mapbox/polyline) to decode it.

View file

@ -60,7 +60,8 @@ With `opt`, you can override the default options, which look like this:
express: true, express: true,
regional: true regional: true
}, },
tickets: false // return tickets? only available with some profiles tickets: false, // return tickets? only available with some profiles
polylines: false // return a shape for each leg?
} }
``` ```
@ -260,3 +261,5 @@ client.journeys(hbf, heinrichHeineStr)
departure of last journey 2017-12-17T19:07:00.000+01:00 departure of last journey 2017-12-17T19:07:00.000+01:00
departure of first (later) journey 2017-12-17T19:19:00.000+01:00 departure of first (later) journey 2017-12-17T19:19:00.000+01:00
``` ```
If you pass `polylines: true`, each journey leg will have a `polyline` field, containing an encoded shape. You can use e.g. [`@mapbox/polyline`](https://www.npmjs.com/package/@mapbox/polyline) to decode it.

View file

@ -8,7 +8,7 @@
This guide is about writing such a profile. If you just want to use an already supported endpoint, refer to the [API documentation](readme.md) instead. This guide is about writing such a profile. If you just want to use an already supported endpoint, refer to the [API documentation](readme.md) instead.
*Note*: **If you get stuck, ask for help by [creating an issue](https://github.com/derhuerst/hafas-client/issues/new)!** We want to help people expand the scope of this library. *Note*: **If you get stuck, ask for help by [creating an issue](https://github.com/public-transport/hafas-client/issues/new)!** We want to help people expand the scope of this library.
## 0. How do the profiles work? ## 0. How do the profiles work?
@ -61,7 +61,7 @@ If you pass this profile into `hafas-client`, the `parseLine` method will overri
1. **Get an iOS or Android device and download the "official" app** for the public transport provider that you want to build a profile for. 1. **Get an iOS or Android device and download the "official" app** for the public transport provider that you want to build a profile for.
2. **Configure a [man-in-the-middle HTTP proxy](https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/)** like [mitmproxy](https://mitmproxy.org). 2. **Configure a [man-in-the-middle HTTP proxy](https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/)** like [mitmproxy](https://mitmproxy.org).
- Configure your device to trust the self-signed SSL certificate, [as outlined in the mitmproxy docs](https://docs.mitmproxy.org/stable/concepts-certificates/). - Configure your device to trust the self-signed SSL certificate, [as outlined in the mitmproxy docs](https://docs.mitmproxy.org/stable/concepts-certificates/).
- *Note*: This method does not work if the app uses [public key pinning](https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning). In this case (the app won't be able to query data), please [create an issue](https://github.com/derhuerst/hafas-client/issues/new), so we can discuss other techniques. - *Note*: This method does not work if the app uses [public key pinning](https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning). In this case (the app won't be able to query data), please [create an issue](https://github.com/public-transport/hafas-client/issues/new), so we can discuss other techniques.
3. **Record requests of the app.** 3. **Record requests of the app.**
- [There's a video showing this step](https://stuff.jannisr.de/how-to-record-hafas-requests.mp4). - [There's a video showing this step](https://stuff.jannisr.de/how-to-record-hafas-requests.mp4).
- Make sure to cover all relevant sections of the app, e.g. "journeys", "departures", "live map". Better record more than less; You will regret not having enough information later on. - Make sure to cover all relevant sections of the app, e.g. "journeys", "departures", "live map". Better record more than less; You will regret not having enough information later on.
@ -74,7 +74,7 @@ If you pass this profile into `hafas-client`, the `parseLine` method will overri
- **Identify the `locale`.** Basically guess work; Use the date & time formats as an indicator. - **Identify the `locale`.** Basically guess work; Use the date & time formats as an indicator.
- **Identify the `timezone`.** This may be tricky, a for example [Deutsche Bahn](https://en.wikipedia.org/wiki/Deutsche_Bahn) returns departures for Moscow as `+01:00` instead of `+03:00`. - **Identify the `timezone`.** This may be tricky, a for example [Deutsche Bahn](https://en.wikipedia.org/wiki/Deutsche_Bahn) returns departures for Moscow as `+01:00` instead of `+03:00`.
- **Copy the authentication** and other meta fields, namely `ver`, `ext`, `client` and `lang`. - **Copy the authentication** and other meta fields, namely `ver`, `ext`, `client` and `lang`.
- You can find these fields in the root of each request JSON. Check [a VBB request](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L13-L22) and [the corresponding VBB profile](https://github.com/derhuerst/hafas-client/blob/6e61097687a37b60d53e767f2711466b80c5142c/p/vbb/index.js#L22-L29) for an example. - You can find these fields in the root of each request JSON. Check [a VBB request](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L13-L22) and [the corresponding VBB profile](https://github.com/public-transport/hafas-client/blob/6e61097687a37b60d53e767f2711466b80c5142c/p/vbb/index.js#L22-L29) for an example.
- Add a function `transformReqBody(body)` to your profile, which assigns them to `body`. - Add a function `transformReqBody(body)` to your profile, which assigns them to `body`.
- Some profiles have a `checksum` parameter (like [here](https://gist.github.com/derhuerst/2a735268bd82a0a6779633f15dceba33#file-journey-details-1-http-L1)) or two `mic` & `mac` parameters (like [here](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L1)). If you see one of them in your requests, jump to [*Appendix A: checksum, mic, mac*](#appendix-a-checksum-mic-mac). Unfortunately, this is necessary to get the profile working. - Some profiles have a `checksum` parameter (like [here](https://gist.github.com/derhuerst/2a735268bd82a0a6779633f15dceba33#file-journey-details-1-http-L1)) or two `mic` & `mac` parameters (like [here](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L1)). If you see one of them in your requests, jump to [*Appendix A: checksum, mic, mac*](#appendix-a-checksum-mic-mac). Unfortunately, this is necessary to get the profile working.
@ -160,10 +160,10 @@ You can just query these, as long as you send a formally correct request.
### endpoints using the `checksum` query parameter ### endpoints using the `checksum` query parameter
`checksum` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code): `hafas-client` will compute it by [hashing](https://en.wikipedia.org/wiki/Hash_function) the request body and a secret *salt*. **This secret can be read from the config file inside the app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/derhuerst/hafas-client/issues/new) instead. `checksum` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code): `hafas-client` will compute it by [hashing](https://en.wikipedia.org/wiki/Hash_function) the request body and a secret *salt*. **This secret can be read from the config file inside the app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/public-transport/hafas-client/issues/new) instead.
### endpoints using the `mic` & `mac` query parameters ### endpoints using the `mic` & `mac` query parameters
`mic` is a [message integrity code](https://en.wikipedia.org/wiki/Message_authentication_code), the [hash](https://en.wikipedia.org/wiki/Hash_function) of the request body. `mic` is a [message integrity code](https://en.wikipedia.org/wiki/Message_authentication_code), the [hash](https://en.wikipedia.org/wiki/Hash_function) of the request body.
`mac` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code), the hash of `mic` and a secret *salt*. **This secret can be read from the config file inside the app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/derhuerst/hafas-client/issues/new) instead. `mac` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code), the hash of `mic` and a secret *salt*. **This secret can be read from the config file inside the app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/public-transport/hafas-client/issues/new) instead.

View file

@ -93,6 +93,7 @@ const createClient = (profile, request = _request) => {
accessibility: 'none', // 'none', 'partial' or 'complete' accessibility: 'none', // 'none', 'partial' or 'complete'
bike: false, // only bike-friendly journeys bike: false, // only bike-friendly journeys
tickets: false, // return tickets? tickets: false, // return tickets?
polylines: false // return leg shapes?
}, opt) }, opt)
if (opt.via) opt.via = profile.formatLocation(profile, opt.via) if (opt.via) opt.via = profile.formatLocation(profile, opt.via)
opt.when = opt.when || new Date() opt.when = opt.when || new Date()
@ -113,7 +114,7 @@ const createClient = (profile, request = _request) => {
// `CGI_READ_FAILED` if you pass `numF`, the parameter for the number // `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
// of results. To circumvent this, we loop here, collecting journeys // of results. To circumvent this, we loop here, collecting journeys
// until we have enough. // until we have enough.
// see https://github.com/derhuerst/hafas-client/pull/23#issuecomment-370246163 // see https://github.com/public-transport/hafas-client/pull/23#issuecomment-370246163
// todo: check if `numF` is supported again, revert this change // todo: check if `numF` is supported again, revert this change
const journeys = [] const journeys = []
const more = (when, journeysRef) => { const more = (when, journeysRef) => {
@ -134,7 +135,7 @@ const createClient = (profile, request = _request) => {
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: !!opt.polylines
} }
if (profile.journeysNumF) query.numF = opt.results if (profile.journeysNumF) query.numF = opt.results
@ -145,7 +146,13 @@ const createClient = (profile, request = _request) => {
}) })
.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)
let polylines = []
if (opt.polylines && Array.isArray(d.common.polyL)) {
polylines = d.common.polyL
}
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks, polylines)
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
let latestDep = -Infinity let latestDep = -Infinity
@ -271,7 +278,8 @@ const createClient = (profile, request = _request) => {
throw new Error('lineName must be a non-empty string.') throw new Error('lineName must be a non-empty string.')
} }
opt = Object.assign({ opt = Object.assign({
passedStations: true // return stations on the way? passedStations: true, // return stations on the way?
polyline: false
}, opt) }, opt)
opt.when = opt.when || new Date() opt.when = opt.when || new Date()
@ -279,13 +287,19 @@ const createClient = (profile, request = _request) => {
cfg: {polyEnc: 'GPA'}, cfg: {polyEnc: 'GPA'},
meth: 'JourneyDetails', meth: 'JourneyDetails',
req: { req: {
// todo: getTrainComposition
jid: ref, jid: ref,
name: lineName, name: lineName,
date: profile.formatDate(profile, opt.when) date: profile.formatDate(profile, opt.when),
getPolyline: !!opt.polyline
} }
}) })
.then((d) => { .then((d) => {
const parse = profile.parseJourneyLeg(profile, d.locations, d.lines, d.remarks) let polylines = []
if (opt.polyline && Array.isArray(d.common.polyL)) {
polylines = d.common.polyL
}
const parse = profile.parseJourneyLeg(profile, d.locations, d.lines, d.remarks, polylines)
const leg = { // pretend the leg is contained in a journey const leg = { // pretend the leg is contained in a journey
type: 'JNY', type: 'JNY',

View file

@ -20,7 +20,7 @@ const request = (profile, data) => {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept-Encoding': 'gzip, deflate', 'Accept-Encoding': 'gzip, deflate',
'user-agent': 'https://github.com/derhuerst/hafas-client' 'user-agent': 'https://github.com/public-transport/hafas-client'
}, },
query: {} query: {}
}) })

View file

@ -6,7 +6,7 @@ const vbbProfile = require('.')
const client = createClient(vbbProfile) const client = createClient(vbbProfile)
// Hauptbahnhof to Charlottenburg // Hauptbahnhof to Charlottenburg
client.journeys('900000003201', '900000024101', {results: 1}) client.journeys('900000003201', '900000024101', {results: 1, polylines: true})
// client.departures('900000013102', {duration: 1}) // client.departures('900000013102', {duration: 1})
// client.locations('Alexanderplatz', {results: 2}) // client.locations('Alexanderplatz', {results: 2})
// client.location('900000042101') // Spichernstr // client.location('900000042101') // Spichernstr
@ -18,6 +18,10 @@ client.journeys('900000003201', '900000024101', {results: 1})
// east: 13.41709 // east: 13.41709
// }, {results: 10}) // }, {results: 10})
// .then(([journey]) => {
// const leg = journey.legs[0]
// return client.journeyLeg(leg.id, leg.line.name, {polyline: true})
// })
.then((data) => { .then((data) => {
console.log(require('util').inspect(data, {depth: null})) console.log(require('util').inspect(data, {depth: null}))
}) })

View file

@ -56,8 +56,8 @@ const parseLocation = (profile, l, lines) => {
return res return res
} }
const createParseJourney = (profile, stations, lines, remarks) => { const createParseJourney = (profile, stations, lines, remarks, polylines) => {
const parseJourney = _createParseJourney(profile, stations, lines, remarks) const parseJourney = _createParseJourney(profile, stations, lines, remarks, polylines)
const parseJourneyWithTickets = (j) => { const parseJourneyWithTickets = (j) => {
const res = parseJourney(j) const res = parseJourney(j)

View file

@ -1,7 +1,7 @@
{ {
"name": "hafas-client", "name": "hafas-client",
"description": "JavaScript client for HAFAS public transport APIs.", "description": "JavaScript client for HAFAS public transport APIs.",
"version": "2.5.0", "version": "2.6.0",
"main": "index.js", "main": "index.js",
"files": [ "files": [
"index.js", "index.js",
@ -16,9 +16,9 @@
"contributors": [ "contributors": [
"Julius Tens <mail@juliustens.eu>" "Julius Tens <mail@juliustens.eu>"
], ],
"homepage": "https://github.com/derhuerst/hafas-client", "homepage": "https://github.com/public-transport/hafas-client",
"repository": "derhuerst/hafas-client", "repository": "public-transport/hafas-client",
"bugs": "https://github.com/derhuerst/hafas-client/issues", "bugs": "https://github.com/public-transport/hafas-client/issues",
"license": "ISC", "license": "ISC",
"keywords": [ "keywords": [
"hafas", "hafas",

View file

@ -1,10 +1,10 @@
'use strict' 'use strict'
// todos from derhuerst/hafas-client#2 // todos from public-transport/hafas-client#2
// - stdStop.dPlatfS, stdStop.dPlatfR // - stdStop.dPlatfS, stdStop.dPlatfR
// todo: what is d.jny.dirFlg? // todo: what is d.jny.dirFlg?
// todo: d.stbStop.dProgType // todo: d.stbStop.dProgType
// todo: d.freq, d.freq.jnyL, see https://github.com/derhuerst/hafas-client/blob/9203ed1481f08baacca41ac5e3c19bf022f01b0b/parse.js#L115 // todo: d.freq, d.freq.jnyL, see https://github.com/public-transport/hafas-client/blob/9203ed1481f08baacca41ac5e3c19bf022f01b0b/parse.js#L115
const createParseDeparture = (profile, stations, lines, remarks) => { const createParseDeparture = (profile, stations, lines, remarks) => {
const findRemark = rm => remarks[parseInt(rm.remX)] || null const findRemark = rm => remarks[parseInt(rm.remX)] || null

View file

@ -4,7 +4,7 @@ const parseDateTime = require('./date-time')
const clone = obj => Object.assign({}, obj) const clone = obj => Object.assign({}, obj)
const createParseJourneyLeg = (profile, stations, lines, remarks) => { const createParseJourneyLeg = (profile, stations, lines, remarks, polylines) => {
// todo: finish parse/remark.js first // todo: finish parse/remark.js first
const applyRemark = (j, rm) => {} const applyRemark = (j, rm) => {}
@ -34,6 +34,13 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => {
res.arrivalDelay = Math.round((realtime - planned) / 1000) res.arrivalDelay = Math.round((realtime - planned) / 1000)
} }
if (pt.jny && pt.jny.polyG) {
let p = pt.jny.polyG.polyXL
p = p && polylines[p[0]]
// todo: there can be >1 polyline
res.polyline = p && p.crdEncYX || null
}
if (pt.type === 'WALK') { if (pt.type === 'WALK') {
res.mode = 'walking' res.mode = 'walking'
res.public = true res.public = true
@ -56,18 +63,25 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => {
for (let remark of pt.jny.remL) applyRemark(j, remark) for (let remark of pt.jny.remL) applyRemark(j, remark)
} }
if (pt.jny.freq && pt.jny.freq.jnyL) { const freq = pt.jny.freq || {}
if (freq.minC && freq.maxC) {
// todo: what is freq.numC?
res.cycle = {
min: freq.minC * 60,
max: freq.maxC * 60
}
}
if (freq.jnyL) {
const parseAlternative = (a) => { const parseAlternative = (a) => {
const t = a.stopL[0].dTimeS || a.stopL[0].dTimeR const t = a.stopL[0].dTimeR || a.stopL[0].dTimeS
const when = profile.parseDateTime(profile, j.date, t) const when = profile.parseDateTime(profile, j.date, t)
// todo: expose a.stopL[0]
return { return {
line: lines[parseInt(a.prodX)] || null, line: lines[parseInt(a.prodX)] || null,
when: when.toISO() when: when.toISO()
} }
} }
res.alternatives = pt.jny.freq.jnyL res.alternatives = freq.jnyL.map(parseAlternative)
.filter(a => a.stopL[0].locX === pt.dep.locX)
.map(parseAlternative)
} }
} }

View file

@ -1,11 +1,9 @@
'use strict' 'use strict'
const createParseJourneyLeg = require('./journey-leg')
const clone = obj => Object.assign({}, obj) const clone = obj => Object.assign({}, obj)
const createParseJourney = (profile, stations, lines, remarks) => { const createParseJourney = (profile, stations, lines, remarks, polylines) => {
const parseLeg = createParseJourneyLeg(profile, stations, lines, remarks) const parseLeg = profile.parseJourneyLeg(profile, stations, lines, remarks, polylines)
// todo: c.sDays // todo: c.sDays
// todo: c.conSubscr // todo: c.conSubscr

View file

@ -28,12 +28,12 @@ const createParseStopover = (profile, stations, lines, remarks, date) => {
Object.defineProperty(res, 'canceled', {value: true}) Object.defineProperty(res, 'canceled', {value: true})
if (st.aCncl) { if (st.aCncl) {
res.arrival = res.arrivalDelay = null res.arrival = res.arrivalDelay = null
const arr = profile.parseDateTime(profile, d.date, st.aTimeS) const arr = profile.parseDateTime(profile, date, st.aTimeS)
res.formerScheduledArrival = arr.toISO() res.formerScheduledArrival = arr.toISO()
} }
if (st.dCncl) { if (st.dCncl) {
res.departure = res.departureDelay = null res.departure = res.departureDelay = null
const arr = profile.parseDateTime(profile, d.date, st.dTimeS) const arr = profile.parseDateTime(profile, date, st.dTimeS)
res.formerScheduledDeparture = arr.toISO() res.formerScheduledDeparture = arr.toISO()
} }
} }

View file

@ -7,12 +7,12 @@ HAFAS endpoint | wrapper library | docs | example code | source code
[Deutsche Bahn (DB)](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`db-hafas`](https://github.com/derhuerst/db-hafas) | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js) [Deutsche Bahn (DB)](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`db-hafas`](https://github.com/derhuerst/db-hafas) | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js)
[Berlin & Brandenburg public transport (VBB)](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas) | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js) [Berlin & Brandenburg public transport (VBB)](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas) | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js)
[Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | [`oebb-hafas`](https://github.com/juliuste/oebb-hafas) | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js) [Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | [`oebb-hafas`](https://github.com/juliuste/oebb-hafas) | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js)
[Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt)/[INSA](https://insa.de) | | [docs](p/insa/readme.md) | [example code](p/insa/example.js) | [src](p/insa/index.js) [Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt)/[INSA](https://insa.de) | [`insa-hafas`](https://github.com/derhuerst/insa-hafas) | [docs](p/insa/readme.md) | [example code](p/insa/example.js) | [src](p/insa/index.js)
[Nahverkehrsverbund Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) | [`nahsh-hafas`](https://github.com/juliuste/nahsh-hafas) | [docs](p/nahsh/readme.md) | [example code](p/nahsh/example.js) | [src](p/nahsh/index.js) [Nahverkehrsverbund Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) | [`nahsh-hafas`](https://github.com/juliuste/nahsh-hafas) | [docs](p/nahsh/readme.md) | [example code](p/nahsh/example.js) | [src](p/nahsh/index.js)
[![npm version](https://img.shields.io/npm/v/hafas-client.svg)](https://www.npmjs.com/package/hafas-client) [![npm version](https://img.shields.io/npm/v/hafas-client.svg)](https://www.npmjs.com/package/hafas-client)
[![build status](https://img.shields.io/travis/derhuerst/hafas-client.svg)](https://travis-ci.org/derhuerst/hafas-client) [![build status](https://img.shields.io/travis/public-transport/hafas-client.svg)](https://travis-ci.org/public-transport/hafas-client)
![ISC-licensed](https://img.shields.io/github/license/derhuerst/hafas-client.svg) ![ISC-licensed](https://img.shields.io/github/license/public-transport/hafas-client.svg)
[![chat on gitter](https://badges.gitter.im/derhuerst.svg)](https://gitter.im/derhuerst) [![chat on gitter](https://badges.gitter.im/derhuerst.svg)](https://gitter.im/derhuerst)
[![support me on Patreon](https://img.shields.io/badge/support%20me-on%20patreon-fa7664.svg)](https://patreon.com/derhuerst) [![support me on Patreon](https://img.shields.io/badge/support%20me-on%20patreon-fa7664.svg)](https://patreon.com/derhuerst)
@ -181,4 +181,4 @@ The returned [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript
## Contributing ## Contributing
If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/derhuerst/hafas-client/issues). If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/public-transport/hafas-client/issues).