├── .editorconfig ├── .github ├── funding.yml └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── contributing.md ├── docs ├── api.md ├── arrivals.md ├── changelog.md ├── departures.md ├── hafas-mgate-api.md ├── journeys-from-trip.md ├── journeys.md ├── lines.md ├── locations.md ├── migrating-to-5.md ├── migrating-to-6.md ├── nearby.md ├── profile-boilerplate.js ├── radar.md ├── reachable-from.md ├── readme.md ├── refresh-journey.md ├── remarks.md ├── server-info.md ├── stop.md ├── tests.md ├── trip.md ├── trips-by-name.md └── writing-a-profile.md ├── eslint.config.js ├── format ├── address.js ├── coord.js ├── date.js ├── filters.js ├── lines-req.js ├── location-filter.js ├── location-identifier.js ├── location.js ├── locations-req.js ├── nearby-req.js ├── poi.js ├── products-filter.js ├── radar-req.js ├── reachable-from-req.js ├── rectangle.js ├── refresh-journey-req.js ├── remarks-req.js ├── station-board-req.js ├── station.js ├── stop-req.js ├── time.js └── trip-req.js ├── index.js ├── lib ├── default-profile.js ├── errors.js ├── find-in-tree.js ├── luxon-timezones.js ├── profile-hooks.js ├── request.js ├── slice-leg.js └── validate-profile.js ├── license.md ├── p ├── avv │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── bart │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── bls │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── bvg │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── cfl │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── cmta │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── dart │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── db-busradar-nrw │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── db │ ├── ageGroup.js │ ├── base.js │ ├── example.js │ ├── index.js │ ├── loyalty-cards.js │ ├── products.js │ ├── readme.md │ └── routing-modes.js ├── insa │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── invg │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── irish-rail │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── ivb │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── kvb │ ├── base.js │ ├── digicert-global-root-g2.crt.pem │ ├── example.js │ ├── index.js │ ├── readme.md │ └── thawte-tls-rsa-ca-g1.crt.pem ├── mobil-nrw │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── mobiliteit-lu │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── nahsh │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── nvv │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── oebb │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── ooevv │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── pkp │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── readme.md ├── rejseplanen │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── rmv │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── rsag │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── saarfahrplan │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── salzburg │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── sbahn-muenchen │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── sncb │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── stv │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── svv │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── tpg │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── vbb │ ├── base.js │ ├── example.js │ ├── index.js │ ├── parse-loc-dhid.js │ ├── products.js │ └── readme.md ├── vbn │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── vkg │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── vmt │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── vor │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── vos │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── vrn │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── vsn │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── vvt │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md ├── vvv │ ├── base.js │ ├── example.js │ ├── index.js │ └── readme.md └── zvv │ ├── base.js │ ├── example.js │ ├── index.js │ ├── products.js │ └── readme.md ├── package.json ├── parse ├── arrival-or-departure.js ├── arrival.js ├── common.js ├── date-time.js ├── departure.js ├── find-remarks.js ├── hint.js ├── icon.js ├── journey-leg.js ├── journey.js ├── line.js ├── location.js ├── movement.js ├── nearby.js ├── operator.js ├── platform.js ├── polyline.js ├── products-bitmask.js ├── prognosis-type.js ├── scheduled-days.js ├── stopover.js ├── trip.js ├── warning.js └── when.js ├── readme.md ├── retry.js ├── test ├── bvg-arrivals.js ├── bvg-journey.js ├── bvg-radar.js ├── bvg-trip-with-occupancy.js ├── db-arrivals.js ├── db-deps-with-destination.js ├── db-journey-2.js ├── db-journey-additional-stopover.js ├── db-journey-fpB-fpE-2-years.js ├── db-journey-overnight.js ├── db-journey-polyline.js ├── db-journey-tzoffset-0.js ├── db-journey.js ├── db-netz-remarks.js ├── db-stop.js ├── e2e │ ├── bls.js │ ├── bvg.js │ ├── cfl.js │ ├── cmta.js │ ├── common.js │ ├── dart.js │ ├── db-busradar-nrw.js │ ├── db.js │ ├── fixtures │ │ └── requests_1722637011 │ │ │ └── recording.har │ ├── insa.js │ ├── invg.js │ ├── ivb.js │ ├── kvb.js │ ├── lib │ │ ├── arrivals.js │ │ ├── departures-in-direction.js │ │ ├── departures.js │ │ ├── earlier-later-journeys.js │ │ ├── journeys-fails-with-no-product.js │ │ ├── journeys-station-to-address.js │ │ ├── journeys-station-to-poi.js │ │ ├── journeys-station-to-station.js │ │ ├── journeys-walking-speed.js │ │ ├── journeys-with-detour.js │ │ ├── leg-cycle-alternatives.js │ │ ├── lines.js │ │ ├── reachable-from.js │ │ ├── refresh-journey.js │ │ ├── remarks.js │ │ ├── server-info.js │ │ ├── util.js │ │ ├── validate-fptf-with.js │ │ ├── validators.js │ │ └── vbb-bvg-validators.js │ ├── mobil-nrw.js │ ├── mobiliteit-lu.js │ ├── nahsh.js │ ├── nvv.js │ ├── oebb.js │ ├── ooevv.js │ ├── pkp.js │ ├── rejseplanen.js │ ├── rmv.js │ ├── rsag.js │ ├── saarfahrplan.js │ ├── salzburg.js │ ├── sbahn-muenchen.js │ ├── sncb.js │ ├── stv.js │ ├── svv.js │ ├── tpg.js │ ├── vbb.js │ ├── vbn.js │ ├── vor.js │ ├── vrn.js │ ├── vsn.js │ ├── vvv.js │ └── zvv.js ├── fixtures │ ├── bvg-arrivals.js │ ├── bvg-arrivals.json │ ├── bvg-journey.js │ ├── bvg-journey.json │ ├── bvg-radar.js │ ├── bvg-radar.json │ ├── bvg-trip-with-occupancy.js │ ├── bvg-trip-with-occupancy.json │ ├── db-arrivals.js │ ├── db-arrivals.json │ ├── db-deps-with-destination.json │ ├── db-journey-2.js │ ├── db-journey-2.json │ ├── db-journey-additional-stopover.json │ ├── db-journey-atzoffset-0.json │ ├── db-journey-dtzoffset-0.json │ ├── db-journey-fpB-fpE-2-years.json │ ├── db-journey-overnight-0.expected.json │ ├── db-journey-overnight-0.json │ ├── db-journey-overnight-1.expected.js │ ├── db-journey-overnight-1.json │ ├── db-journey-polyline.js │ ├── db-journey-polyline.json │ ├── db-journey.js │ ├── db-journey.json │ ├── db-netz-remarks.json │ ├── db-rest-journey.json │ ├── db-stop.js │ ├── db-stop.json │ ├── error-h9360.json │ ├── error-location.json │ ├── error-no-match.json │ ├── error-parameter.json │ ├── insa-stop.js │ ├── insa-stop.json │ ├── oebb-trip.js │ ├── oebb-trip.json │ ├── rejseplanen-trip.js │ ├── rejseplanen-trip.json │ ├── rsag-journey.js │ ├── rsag-journey.json │ ├── sncb-journey-with-chki.json │ ├── sncb-stop.json │ ├── vbb-departures.js │ ├── vbb-departures.json │ ├── vbb-journeys.js │ ├── vbb-journeys.json │ ├── vbb-on-demand-trip.js │ ├── vbb-on-demand-trip.json │ ├── vsn-departures.js │ ├── vsn-departures.json │ ├── vsn-remarks.js │ └── vsn-remarks.json ├── format │ ├── db-journeys-query.js │ └── products-filter.js ├── insa-stop.js ├── lib │ └── request.js ├── mobiliteit-lu-line.js ├── oebb-trip.js ├── parse │ ├── date-time.js │ ├── hint.js │ ├── icon.js │ ├── line.js │ ├── location.js │ ├── operator.js │ ├── warning.js │ └── when.js ├── rejseplanen-trip.js ├── retry.js ├── rsag-journey.js ├── sncb-journey-with-chki.js ├── throttle.js ├── vbb-departures.js ├── vbb-journeys.js ├── vbb-on-demand-trip.js ├── vsn-departures.js └── vsn-remarks.js ├── throttle.js └── tools ├── debug-cli ├── cli.js └── package.json ├── endpoint-hci-version ├── cli.js └── package.json └── pull-profile-base-data.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | # Use tabs in JavaScript and JSON. 11 | [**.{js,json}] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | # Use spaces in YAML. 16 | [**.{yml,yaml}] 17 | indent_style = spaces 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | liberapay: derhuerst 2 | patreon: derhuerst 3 | github: derhuerst 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | .nvm-version 5 | node_modules 6 | npm-debug.log 7 | 8 | package-lock.json 9 | 10 | /.tap 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tools/transport-apis"] 2 | path = tools/transport-apis 3 | url = https://github.com/public-transport/transport-apis.git 4 | branch = v1 5 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for helping! 🙏 4 | 5 | ## Adding integration/end-to-end tests 6 | 7 | Refer to the [testing docs](docs/tests.md). 8 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # `hafas-client` API 2 | 3 | - [`journeys(from, to, [opt])`](journeys.md) – get journeys between locations 4 | - [`refreshJourney(refreshToken, [opt])`](refresh-journey.md) – fetch up-to-date/more details of a `journey` 5 | - [`journeysFromTrip(tripId, previousStopover, to, [opt])`](journeys-from-trip.md) – get journeys from a trip to a location 6 | - [`trip(id, lineName, [opt])`](trip.md) – get details for a trip 7 | - [`tripsByName(lineNameOrFahrtNr, [opt])`](trips-by-name.md) – get all trips matching a name 8 | - [`departures(station, [opt])`](departures.md) – query the next departures at a station 9 | - [`arrivals(station, [opt])`](arrivals.md) – query the next arrivals at a station 10 | - [`locations(query, [opt])`](locations.md) – find stations, POIs and addresses 11 | - [`stop(id, [opt])`](stop.md) – get details about a stop/station 12 | - [`nearby(location, [opt])`](nearby.md) – show stations & POIs around 13 | - [`radar(north, west, south, east, [opt])`](radar.md) – find all vehicles currently in a certain area 14 | - [`reachableFrom(address, [opt])`](reachable-from.md) – get all stations reachable from an address within `n` minutes 15 | - [`remarks([opt])`](remarks.md) – get all remarks 16 | - [`lines(query, [opt])`](lines.md) – get all lines matching a name 17 | - [`serverInfo([opt])`](server-info.md) – fetch meta information from HAFAS 18 | -------------------------------------------------------------------------------- /docs/arrivals.md: -------------------------------------------------------------------------------- 1 | # `arrivals(station, [opt])` 2 | 3 | Just like [`departures(station, [opt])`](departures.md), except that it resolves with arrival times instead of departure times. 4 | -------------------------------------------------------------------------------- /docs/profile-boilerplate.js: -------------------------------------------------------------------------------- 1 | // Refer to the the ./writing-a-profile.md guide. 2 | 3 | const products = [ 4 | { 5 | id: 'commuterTrain', 6 | mode: 'train', 7 | bitmasks: [16], 8 | name: 'ACME Commuter Rail', 9 | short: 'CR', 10 | default: true, 11 | }, 12 | { 13 | id: 'metro', 14 | mode: 'train', 15 | bitmasks: [8], 16 | name: 'Foo Bar Metro', 17 | short: 'M', 18 | default: true, 19 | }, 20 | ]; 21 | 22 | const transformReqBody = (body) => { 23 | // get these from the recorded app requests 24 | // body.client = { … } 25 | // body.ver = … 26 | // body.auth = { … } 27 | // body.lang = … 28 | return body; 29 | }; 30 | 31 | const insaProfile = { 32 | // locale: …, 33 | // timezone: …, 34 | // endpoint: …, 35 | transformReqBody, 36 | 37 | products: products, 38 | 39 | trip: false, 40 | radar: false, 41 | }; 42 | 43 | export { 44 | insaProfile, 45 | }; 46 | -------------------------------------------------------------------------------- /docs/server-info.md: -------------------------------------------------------------------------------- 1 | # `serverInfo([opt])` 2 | 3 | **Fetches meta information from the HAFAS endpoint.** 4 | 5 | With `opt`, you can override the default options, which look like this: 6 | 7 | ```js 8 | { 9 | versionInfo: true, // query HAFAS versions? 10 | language: 'en', // depends on the profile 11 | } 12 | ``` 13 | 14 | ## Example 15 | 16 | As an example, we're going to use the [SVV profile](../p/svv): 17 | 18 | ```js 19 | import {createClient} from 'hafas-client' 20 | import {profile as svvProfile} from 'hafas-client/p/svv/index.js' 21 | 22 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 23 | const client = createClient(svvProfile, userAgent) 24 | 25 | await client.serverInfo() 26 | ``` 27 | 28 | ```js 29 | { 30 | // version of the HAFAS Connection Interface (HCI), the API that hafas-client uses 31 | hciVersion: '1.23', 32 | 33 | timetableStart: '20200517', 34 | timetableEnd: '20201212', 35 | serverTime: '2020-07-19T21:32:12+02:00', 36 | realtimeDataUpdatedAt: 1595187102, 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /format/address.js: -------------------------------------------------------------------------------- 1 | import {formatLocationIdentifier} from './location-identifier.js'; 2 | import {formatCoord} from './coord.js'; 3 | 4 | const formatAddress = (a) => { 5 | if (a.type !== 'location' || !a.latitude || !a.longitude || !a.address) { 6 | throw new TypeError('invalid address'); 7 | } 8 | 9 | const data = { 10 | A: '2', // address? 11 | O: a.address, 12 | X: formatCoord(a.longitude), 13 | Y: formatCoord(a.latitude), 14 | }; 15 | if (a.id) { 16 | data.L = a.id; 17 | } 18 | return { 19 | type: 'A', // address 20 | name: a.address, 21 | lid: formatLocationIdentifier(data), 22 | }; 23 | }; 24 | 25 | export { 26 | formatAddress, 27 | }; 28 | -------------------------------------------------------------------------------- /format/coord.js: -------------------------------------------------------------------------------- 1 | const formatCoord = x => Math.round(x * 1000000); 2 | 3 | export { 4 | formatCoord, 5 | }; 6 | -------------------------------------------------------------------------------- /format/date.js: -------------------------------------------------------------------------------- 1 | import {DateTime, IANAZone} from 'luxon'; 2 | import {luxonIANAZonesByProfile as timezones} from '../lib/luxon-timezones.js'; 3 | 4 | // todo: change to `(profile) => (when) => {}` 5 | const formatDate = (profile, when) => { 6 | let timezone; 7 | if (timezones.has(profile)) { 8 | timezone = timezones.get(profile); 9 | } else { 10 | timezone = new IANAZone(profile.timezone); 11 | timezones.set(profile, timezone); 12 | } 13 | 14 | return DateTime 15 | .fromMillis(Number(when), { 16 | locale: profile.locale, 17 | zone: timezone, 18 | }) 19 | .toFormat('yyyyMMdd'); 20 | }; 21 | 22 | export { 23 | formatDate, 24 | }; 25 | -------------------------------------------------------------------------------- /format/filters.js: -------------------------------------------------------------------------------- 1 | const bike = {type: 'BC', mode: 'INC'}; 2 | 3 | const accessibility = { 4 | none: {type: 'META', mode: 'INC', meta: 'notBarrierfree'}, 5 | partial: {type: 'META', mode: 'INC', meta: 'limitedBarrierfree'}, 6 | complete: {type: 'META', mode: 'INC', meta: 'completeBarrierfree'}, 7 | }; 8 | 9 | export { 10 | bike, 11 | accessibility, 12 | }; 13 | -------------------------------------------------------------------------------- /format/lines-req.js: -------------------------------------------------------------------------------- 1 | const formatLinesReq = (ctx, query) => { 2 | return { 3 | meth: 'LineMatch', 4 | req: { 5 | input: query, 6 | }, 7 | }; 8 | }; 9 | 10 | export { 11 | formatLinesReq, 12 | }; 13 | -------------------------------------------------------------------------------- /format/location-filter.js: -------------------------------------------------------------------------------- 1 | const formatLocationFilter = (stops, addresses, poi) => { 2 | if (stops && addresses && poi) { 3 | return 'ALL'; 4 | } 5 | return ( 6 | (stops ? 'S' : '') 7 | + (addresses ? 'A' : '') 8 | + (poi ? 'P' : '') 9 | ); 10 | }; 11 | 12 | export { 13 | formatLocationFilter, 14 | }; 15 | -------------------------------------------------------------------------------- /format/location-identifier.js: -------------------------------------------------------------------------------- 1 | const sep = '@'; 2 | 3 | const formatLocationIdentifier = (data) => { 4 | let str = ''; 5 | for (let key in data) { 6 | if (!Object.prototype.hasOwnProperty.call(data, key)) { 7 | continue; 8 | } 9 | 10 | str += key + '=' + data[key] + sep; // todo: escape, but how? 11 | } 12 | 13 | return str; 14 | }; 15 | 16 | export { 17 | formatLocationIdentifier, 18 | }; 19 | -------------------------------------------------------------------------------- /format/location.js: -------------------------------------------------------------------------------- 1 | const formatLocation = (profile, l, name = 'location') => { 2 | if ('string' === typeof l) { 3 | return profile.formatStation(l); 4 | } 5 | if ('object' === typeof l && !Array.isArray(l)) { 6 | if (l.type === 'station' || l.type === 'stop') { 7 | return profile.formatStation(l.id); 8 | } 9 | if (l.poi) { 10 | return profile.formatPoi(l); 11 | } 12 | if ('string' === typeof l.address) { 13 | return profile.formatAddress(l); 14 | } 15 | if (!l.type) { 16 | throw new TypeError(`missing ${name}.type`); 17 | } 18 | throw new TypeError(`invalid ${name}.type: ${l.type}`); 19 | } 20 | throw new TypeError(name + ': valid station, address or poi required.'); 21 | }; 22 | 23 | export { 24 | formatLocation, 25 | }; 26 | -------------------------------------------------------------------------------- /format/locations-req.js: -------------------------------------------------------------------------------- 1 | const formatLocationsReq = (ctx, query) => { 2 | const {profile, opt} = ctx; 3 | 4 | return { 5 | cfg: {polyEnc: 'GPA'}, 6 | meth: 'LocMatch', 7 | req: {input: { 8 | loc: { 9 | type: profile.formatLocationFilter(opt.stops, opt.addresses, opt.poi), 10 | name: opt.fuzzy 11 | ? query + '?' 12 | : query, 13 | }, 14 | maxLoc: opt.results, 15 | field: 'S', // todo: what is this? 16 | }}, 17 | }; 18 | }; 19 | 20 | export { 21 | formatLocationsReq, 22 | }; 23 | -------------------------------------------------------------------------------- /format/nearby-req.js: -------------------------------------------------------------------------------- 1 | const formatNearbyReq = (ctx, location) => { 2 | const {profile, opt} = ctx; 3 | 4 | return { 5 | cfg: {polyEnc: 'GPA'}, 6 | meth: 'LocGeoPos', 7 | req: { 8 | ring: { 9 | cCrd: { 10 | x: profile.formatCoord(location.longitude), 11 | y: profile.formatCoord(location.latitude), 12 | }, 13 | maxDist: opt.distance || -1, 14 | minDist: 0, 15 | }, 16 | locFltrL: [ 17 | profile.formatProductsFilter(ctx, opt.products || {}), 18 | ], 19 | getPOIs: Boolean(opt.poi), 20 | getStops: Boolean(opt.stops), 21 | maxLoc: opt.results, 22 | }, 23 | }; 24 | }; 25 | 26 | export { 27 | formatNearbyReq, 28 | }; 29 | -------------------------------------------------------------------------------- /format/poi.js: -------------------------------------------------------------------------------- 1 | import {formatLocationIdentifier} from './location-identifier.js'; 2 | import {formatCoord} from './coord.js'; 3 | 4 | const formatPoi = (p) => { 5 | // todo: use Number.isFinite()! 6 | if (p.type !== 'location' || !p.latitude || !p.longitude || !p.id || !p.name) { 7 | throw new TypeError('invalid POI'); 8 | } 9 | 10 | return { 11 | type: 'P', // POI 12 | name: p.name, 13 | lid: formatLocationIdentifier({ 14 | A: '4', // POI? 15 | O: p.name, 16 | L: p.id, 17 | X: formatCoord(p.longitude), 18 | Y: formatCoord(p.latitude), 19 | }), 20 | }; 21 | }; 22 | 23 | export { 24 | formatPoi, 25 | }; 26 | -------------------------------------------------------------------------------- /format/products-filter.js: -------------------------------------------------------------------------------- 1 | import isObj from 'lodash/isObject.js'; 2 | 3 | const hasProp = (o, k) => Object.prototype.hasOwnProperty.call(o, k); 4 | 5 | const formatProductsFilter = (ctx, filter) => { 6 | if (!isObj(filter)) { 7 | throw new TypeError('products filter must be an object'); 8 | } 9 | const {profile} = ctx; 10 | 11 | const byProduct = {}; 12 | const defaultProducts = {}; 13 | for (let product of profile.products) { 14 | byProduct[product.id] = product; 15 | defaultProducts[product.id] = product.default; 16 | } 17 | filter = Object.assign({}, defaultProducts, filter); 18 | 19 | let res = 0, products = 0; 20 | for (let product in filter) { 21 | if (!hasProp(filter, product) || filter[product] !== true) { 22 | continue; 23 | } 24 | if (!byProduct[product]) { 25 | throw new TypeError('unknown product ' + product); 26 | } 27 | products++; 28 | for (let bitmask of byProduct[product].bitmasks) { 29 | res = res | bitmask; 30 | } 31 | } 32 | if (products === 0) { 33 | throw new Error('no products used'); 34 | } 35 | 36 | return { 37 | type: 'PROD', 38 | mode: 'INC', 39 | value: String(res), 40 | }; 41 | }; 42 | 43 | export { 44 | formatProductsFilter, 45 | }; 46 | -------------------------------------------------------------------------------- /format/radar-req.js: -------------------------------------------------------------------------------- 1 | const formatRadarReq = (ctx, north, west, south, east) => { 2 | const {profile, opt} = ctx; 3 | 4 | return { 5 | meth: 'JourneyGeoPos', 6 | req: { 7 | maxJny: opt.results, 8 | onlyRT: false, // todo: does this mean "only realtime"? 9 | date: profile.formatDate(profile, opt.when), 10 | time: profile.formatTime(profile, opt.when), 11 | // todo: would a ring work here as well? 12 | rect: profile.formatRectangle(profile, north, west, south, east), 13 | perSize: opt.duration * 1000, 14 | perStep: Math.round(opt.duration / Math.max(opt.frames, 1) * 1000), 15 | ageOfReport: true, // todo: what is this? 16 | jnyFltrL: [ 17 | profile.formatProductsFilter(ctx, opt.products || {}), 18 | ], 19 | // todo: what is this? what about realtime? 20 | // - CALC 21 | // - CALC_REPORT (as seen in the INSA Young app) 22 | trainPosMode: 'CALC', 23 | }, 24 | }; 25 | }; 26 | 27 | export { 28 | formatRadarReq, 29 | }; 30 | -------------------------------------------------------------------------------- /format/reachable-from-req.js: -------------------------------------------------------------------------------- 1 | const formatReachableFromReq = (ctx, address) => { 2 | const {profile, opt} = ctx; 3 | 4 | return { 5 | meth: 'LocGeoReach', 6 | req: { 7 | loc: profile.formatLocation(profile, address, 'address'), 8 | maxDur: opt.maxDuration === null 9 | ? -1 10 | : opt.maxDuration, 11 | maxChg: opt.maxTransfers, 12 | date: profile.formatDate(profile, opt.when), 13 | time: profile.formatTime(profile, opt.when), 14 | period: 120, // todo: what is this? 15 | jnyFltrL: [ 16 | profile.formatProductsFilter(ctx, opt.products || {}), 17 | ], 18 | }, 19 | }; 20 | }; 21 | 22 | export { 23 | formatReachableFromReq, 24 | }; 25 | -------------------------------------------------------------------------------- /format/rectangle.js: -------------------------------------------------------------------------------- 1 | const formatRectangle = (profile, north, west, south, east) => { 2 | return { 3 | llCrd: { 4 | x: profile.formatCoord(west), 5 | y: profile.formatCoord(south), 6 | }, 7 | urCrd: { 8 | x: profile.formatCoord(east), 9 | y: profile.formatCoord(north), 10 | }, 11 | }; 12 | }; 13 | 14 | export { 15 | formatRectangle, 16 | }; 17 | -------------------------------------------------------------------------------- /format/refresh-journey-req.js: -------------------------------------------------------------------------------- 1 | const formatRefreshJourneyReq = (ctx, refreshToken) => { 2 | const {profile, opt} = ctx; 3 | 4 | const req = { 5 | getIST: true, // todo: make an option 6 | getPasslist: Boolean(opt.stopovers), 7 | getPolyline: Boolean(opt.polylines), 8 | getTariff: Boolean(opt.tickets), 9 | }; 10 | if (profile.refreshJourneyUseOutReconL) { 11 | req.outReconL = [{ctx: refreshToken}]; 12 | } else { 13 | req.ctxRecon = refreshToken; 14 | } 15 | 16 | return { 17 | meth: 'Reconstruction', 18 | req, 19 | }; 20 | }; 21 | 22 | export { 23 | formatRefreshJourneyReq, 24 | }; 25 | -------------------------------------------------------------------------------- /format/remarks-req.js: -------------------------------------------------------------------------------- 1 | const formatRemarksReq = (ctx) => { 2 | const {profile, opt} = ctx; 3 | 4 | const himFltrL = []; 5 | // todo: https://github.com/marudor/BahnhofsAbfahrten/blob/95fef0217d01344642dd423457473fe9b8b6056e/src/types/HAFAS/index.ts#L76-L91 6 | if (opt.products) { 7 | himFltrL.push(profile.formatProductsFilter(ctx, opt.products)); 8 | } 9 | 10 | const req = { 11 | himFltrL, 12 | }; 13 | if (profile.remarksGetPolyline) { 14 | req.getPolyline = Boolean(opt.polylines); 15 | } 16 | // todo: stLoc, dirLoc 17 | // todo: comp, dept, onlyHimId, onlyToday 18 | // todo: dailyB, dailyE 19 | // see https://github.com/marudor/BahnhofsAbfahrten/blob/46a74957d68edc15713112df44e1a25150f5a178/src/types/HAFAS/HimSearch.ts#L3-L18 20 | 21 | if (opt.results !== null) { 22 | req.maxNum = opt.results; 23 | } 24 | if (opt.from !== null) { 25 | req.dateB = profile.formatDate(profile, opt.from); 26 | req.timeB = profile.formatTime(profile, opt.from); 27 | } 28 | if (opt.to !== null) { 29 | req.dateE = profile.formatDate(profile, opt.to); 30 | req.timeE = profile.formatTime(profile, opt.to); 31 | } 32 | 33 | return {meth: 'HimSearch', req}; 34 | }; 35 | 36 | export { 37 | formatRemarksReq, 38 | }; 39 | -------------------------------------------------------------------------------- /format/station-board-req.js: -------------------------------------------------------------------------------- 1 | const formatStationBoardReq = (ctx, station, type) => { 2 | const {profile, opt} = ctx; 3 | 4 | const jnyFltrL = [ 5 | profile.formatProductsFilter(ctx, opt.products || {}), 6 | ]; 7 | if (opt.line !== null) { 8 | jnyFltrL.push({type: 'LINEID', mode: 'INC', value: opt.line}); 9 | } 10 | 11 | const req = { 12 | type, 13 | date: profile.formatDate(profile, opt.when), 14 | time: profile.formatTime(profile, opt.when), 15 | stbLoc: station, 16 | dirLoc: opt.direction 17 | ? profile.formatStation(opt.direction) 18 | : undefined, 19 | jnyFltrL, 20 | dur: opt.duration, 21 | }; 22 | if (opt.results !== null) { 23 | req.maxJny = opt.results === Infinity 24 | ? 10000 25 | : opt.results; 26 | } 27 | if (profile.departuresGetPasslist) { 28 | req.getPasslist = Boolean(opt.stopovers); 29 | } 30 | if (profile.departuresStbFltrEquiv) { 31 | req.stbFltrEquiv = !opt.includeRelatedStations; 32 | } 33 | 34 | return { 35 | meth: 'StationBoard', 36 | req, 37 | }; 38 | }; 39 | 40 | export { 41 | formatStationBoardReq, 42 | }; 43 | -------------------------------------------------------------------------------- /format/station.js: -------------------------------------------------------------------------------- 1 | import {formatLocationIdentifier} from './location-identifier.js'; 2 | 3 | const formatStation = (id) => { 4 | return { 5 | type: 'S', // station 6 | // todo: name necessary? 7 | lid: formatLocationIdentifier({ 8 | A: '1', // station? 9 | L: id, 10 | // todo: `p` – timestamp of when the ID was obtained 11 | }), 12 | }; 13 | }; 14 | 15 | export { 16 | formatStation, 17 | }; 18 | -------------------------------------------------------------------------------- /format/stop-req.js: -------------------------------------------------------------------------------- 1 | const formatStopReq = (ctx, stopRef) => { 2 | return { 3 | // todo: there's also `StationDetails`, are there differences? 4 | meth: 'LocDetails', 5 | req: { 6 | locL: [stopRef], 7 | }, 8 | }; 9 | }; 10 | 11 | export { 12 | formatStopReq, 13 | }; 14 | -------------------------------------------------------------------------------- /format/time.js: -------------------------------------------------------------------------------- 1 | import {DateTime, IANAZone} from 'luxon'; 2 | import {luxonIANAZonesByProfile as timezones} from '../lib/luxon-timezones.js'; 3 | 4 | // todo: change to `(profile) => (when) => {}` 5 | const formatTime = (profile, when) => { 6 | let timezone; 7 | if (timezones.has(profile)) { 8 | timezone = timezones.get(profile); 9 | } else { 10 | timezone = new IANAZone(profile.timezone); 11 | timezones.set(profile, timezone); 12 | } 13 | 14 | return DateTime 15 | .fromMillis(Number(when), { 16 | locale: profile.locale, 17 | zone: timezone, 18 | }) 19 | .toFormat('HHmmss'); 20 | }; 21 | 22 | export { 23 | formatTime, 24 | }; 25 | -------------------------------------------------------------------------------- /format/trip-req.js: -------------------------------------------------------------------------------- 1 | const formatTripReq = ({opt}, id) => { 2 | return { 3 | cfg: {polyEnc: 'GPA'}, 4 | meth: 'JourneyDetails', 5 | req: { 6 | // todo: getTrainComposition 7 | jid: id, 8 | // HAFAS apparently ignores the date in the trip ID and uses the `date` field. 9 | // Thus, it will find a different trip if you pass the wrong date via `opt.when`. 10 | // date: profile.formatDate(profile, opt.when), 11 | getPolyline: Boolean(opt.polyline), 12 | }, 13 | }; 14 | }; 15 | 16 | export { 17 | formatTripReq, 18 | }; 19 | -------------------------------------------------------------------------------- /lib/find-in-tree.js: -------------------------------------------------------------------------------- 1 | import objectScan from 'object-scan'; 2 | 3 | const createFindInTree = (needles) => { 4 | const scanner = objectScan(needles, { 5 | filterFn: ({value, parents, matchedBy, context}) => { 6 | matchedBy.forEach((needle) => { 7 | context[needle].push([value, parents]); 8 | }); 9 | }, 10 | }); 11 | 12 | const findInTree = (haystack) => { 13 | const context = Object.create(null); 14 | needles.forEach((needle) => { 15 | context[needle] = []; 16 | }); 17 | return scanner(haystack, context); 18 | }; 19 | return findInTree; 20 | }; 21 | 22 | export { 23 | createFindInTree, 24 | }; 25 | -------------------------------------------------------------------------------- /lib/luxon-timezones.js: -------------------------------------------------------------------------------- 1 | // hafas-client profile -> luxon.IANAZone 2 | const luxonIANAZonesByProfile = new WeakMap(); 3 | 4 | export { 5 | luxonIANAZonesByProfile, 6 | }; 7 | -------------------------------------------------------------------------------- /lib/profile-hooks.js: -------------------------------------------------------------------------------- 1 | // For any type of "thing to parse", there's >=1 parse functions. 2 | // By composing custom parse function(s) with the default ones, one 3 | // can customize the behaviour of hafas-client. Profiles extensively 4 | // use this mechanism. 5 | 6 | // Each parse function has the following signature: 7 | // ({opt, profile, common, res}, ...raw) => newParsed 8 | 9 | // Compose a new/custom parse function with the old/existing parse 10 | // function, so that the new fn will be called with the output of the 11 | // old fn. 12 | const parseHook = (oldParse, newParse) => { 13 | return (ctx, ...args) => { 14 | return newParse({ 15 | ...ctx, 16 | parsed: oldParse({...ctx, parsed: {}}, ...args), 17 | }, ...args); 18 | }; 19 | }; 20 | 21 | export { 22 | parseHook, 23 | }; 24 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025, Jannis R 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /p/avv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: '4vV1AcH3N511icH', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'AVV_AACHEN', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://auskunft.avv.de/bin/mgate.exe', 12 | defaultLanguage: 'de', 13 | }; 14 | -------------------------------------------------------------------------------- /p/avv/readme.md: -------------------------------------------------------------------------------- 1 | # AVV profile for `hafas-client` 2 | 3 | [*Aachener Verkehrsverbund (AVV)*](https://de.wikipedia.org/wiki/Aachener_Verkehrsverbund) is the local transport provider of [Aachen](https://en.wikipedia.org/wiki/Aachen). This profile adds *AVV* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as avvProfile} from 'hafas-client/p/avv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with AVV profile 14 | const client = createClient(avvProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /p/bart/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'kEwHkFUCIL500dym', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'BART', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://planner.bart.gov/bin/mgate.exe', 12 | defaultLanguage: 'en', 13 | }; 14 | -------------------------------------------------------------------------------- /p/bart/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | 3 | const products = [{ 4 | id: 'bart', 5 | mode: 'train', 6 | bitmasks: [128], 7 | name: 'BART', 8 | short: 'BART', 9 | default: true, 10 | }, { 11 | id: 'regional-train', 12 | mode: 'train', 13 | bitmasks: [8], 14 | name: 'regional trains (Caltrain, Capitol Corridor, ACE)', 15 | short: 'regional trains', 16 | default: true, 17 | }, { 18 | id: 'bus', 19 | mode: 'bus', 20 | bitmasks: [32], 21 | name: 'Bus', 22 | short: 'Bus', 23 | default: true, 24 | }, { 25 | id: 'ferry', 26 | mode: 'watercraft', 27 | bitmasks: [64], 28 | name: 'Ferry', 29 | short: 'Ferry', 30 | default: true, 31 | }, { 32 | id: 'tram', 33 | mode: 'train', 34 | bitmasks: [256], 35 | name: 'Tram', 36 | short: 'Tram', 37 | default: true, 38 | }, { 39 | id: 'cable-car', 40 | mode: 'train', 41 | bitmasks: [4], 42 | name: 'cable car', 43 | short: 'cable car', 44 | default: true, 45 | }]; 46 | 47 | const profile = { 48 | ...baseProfile, 49 | locale: 'en-US', 50 | timezone: 'America/Los_Angeles', 51 | ver: '1.40', 52 | 53 | products, 54 | 55 | trip: true, 56 | radar: true, 57 | reachableFrom: true, 58 | 59 | refreshJourneyUseOutReconL: true, 60 | }; 61 | 62 | export { 63 | profile, 64 | }; 65 | -------------------------------------------------------------------------------- /p/bart/readme.md: -------------------------------------------------------------------------------- 1 | # BART profile for `hafas-client` 2 | 3 | [*Bay Area Rapid Transit (BART)*](https://en.wikipedia.org/wiki/Bay_Area_Rapid_Transit) is the rapid transit public transportation system serving the [San Francisco Bay Area](https://en.wikipedia.org/wiki/San_Francisco_Bay_Area). This profile adds *BART* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as bartProfile} from 'hafas-client/p/bart/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with BART profile 14 | const client = createClient(bartProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /p/bls/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: '3jkAncud78HSoqclmN54812A', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'HAFAS', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://bls.hafas.de/bin/mgate.exe', 12 | defaultLanguage: 'de', 13 | }; 14 | -------------------------------------------------------------------------------- /p/bls/readme.md: -------------------------------------------------------------------------------- 1 | # BLS profile for `hafas-client` 2 | 3 | [*BLS AG*](https://en.wikipedia.org/wiki/BLS_AG) is the local transport provider of the [Canton of Bern](https://en.wikipedia.org/wiki/Canton_of_Bern). This profile adds *BLS* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as blsProfile} from 'hafas-client/p/bls/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with BLS profile 14 | const client = createClient(blsProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/bvg/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'dVg4TZbW8anjx9ztPwe2uk4LVRi9wO', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VBB', 9 | v: 10002, 10 | name: 'webapp', 11 | }, 12 | endpoint: 'https://bvg-apps-ext.hafas.de/bin/mgate.exe', 13 | ext: 'BVG.1', 14 | ver: '1.72', 15 | defaultLanguage: 'de', 16 | }; 17 | -------------------------------------------------------------------------------- /p/bvg/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'suburban', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'S-Bahn', 7 | short: 'S', 8 | default: true, 9 | }, 10 | { 11 | id: 'subway', 12 | mode: 'train', 13 | bitmasks: [2], 14 | name: 'U-Bahn', 15 | short: 'U', 16 | default: true, 17 | }, 18 | { 19 | id: 'tram', 20 | mode: 'train', 21 | bitmasks: [4], 22 | name: 'Tram', 23 | short: 'T', 24 | default: true, 25 | }, 26 | { 27 | id: 'bus', 28 | mode: 'bus', 29 | bitmasks: [8], 30 | name: 'Bus', 31 | short: 'B', 32 | default: true, 33 | }, 34 | { 35 | id: 'ferry', 36 | mode: 'watercraft', 37 | bitmasks: [16], 38 | name: 'Fähre', 39 | short: 'F', 40 | default: true, 41 | }, 42 | { 43 | id: 'express', 44 | mode: 'train', 45 | bitmasks: [32], 46 | name: 'IC/ICE', 47 | short: 'E', 48 | default: true, 49 | }, 50 | { 51 | id: 'regional', 52 | mode: 'train', 53 | bitmasks: [64], 54 | name: 'RB/RE', 55 | short: 'R', 56 | default: true, 57 | }, 58 | ]; 59 | 60 | export { 61 | products, 62 | }; 63 | -------------------------------------------------------------------------------- /p/bvg/readme.md: -------------------------------------------------------------------------------- 1 | # BVG profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Berlin-Brandenburg (BVG)*](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) is the major local transport provider in [Berlin](https://en.wikipedia.org/wiki/Berlin). This profile adds *BVG*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as bvgProfile} from 'hafas-client/p/bvg/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with BVG profile 14 | const client = createClient(bvgProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *BVG*-specific products (such as *X-Bus*) 21 | - strips parts from station names that are unnecessary in the Berlin context 22 | - parses line names to give more information (e.g. "Is it an express bus?") 23 | - renames *Ringbahn* line names to contain `⟳` and `⟲` 24 | -------------------------------------------------------------------------------- /p/cfl/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'ALT2vl7LAFDFu2dz', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'HAFAS', 9 | v: '4000000', 10 | name: 'cflPROD-STORE', 11 | }, 12 | endpoint: 'https://horaires.cfl.lu/bin/mgate.exe', 13 | ver: '1.43', 14 | defaultLanguage: 'fr', 15 | }; 16 | -------------------------------------------------------------------------------- /p/cfl/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-LU', 7 | timezone: 'Europe/Luxembourg', 8 | defaultLanguage: 'de', 9 | 10 | products: products, 11 | 12 | refreshJourneyUseOutReconL: true, 13 | trip: true, 14 | radar: true, 15 | reachableFrom: true, 16 | remarksGetPolyline: false, 17 | }; 18 | 19 | export { 20 | profile, 21 | }; 22 | -------------------------------------------------------------------------------- /p/cfl/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | // todo: other bits 3 | { 4 | id: 'express-train', 5 | mode: 'train', 6 | bitmasks: [1, 2], 7 | name: 'TGV, ICE, EuroCity', 8 | short: 'TGV/ICE/EC', 9 | default: true, 10 | }, 11 | { 12 | id: 'local-train', 13 | mode: 'train', 14 | bitmasks: [8, 16], 15 | name: 'local trains', 16 | short: 'local', 17 | default: true, 18 | }, 19 | { 20 | id: 'tram', 21 | mode: 'train', 22 | bitmasks: [256], 23 | name: 'tram', 24 | short: 'tram', 25 | default: true, 26 | }, 27 | { 28 | id: 'bus', 29 | mode: 'bus', 30 | bitmasks: [32], 31 | name: 'bus', 32 | short: 'bus', 33 | default: true, 34 | }, 35 | { 36 | id: 'gondola', 37 | mode: 'gondola', 38 | bitmasks: [512], 39 | name: 'Fun', // taken from the horaires.cfl.lu website 40 | short: 'Fun', // abbreviation for funicular? 41 | default: true, 42 | }, 43 | ]; 44 | 45 | export { 46 | products, 47 | }; 48 | -------------------------------------------------------------------------------- /p/cfl/readme.md: -------------------------------------------------------------------------------- 1 | # CFL profile for `hafas-client` 2 | 3 | The [*Société Nationale des Chemins de Fer Luxembourgeois (CFL)*](https://en.wikipedia.org/wiki/Société_Nationale_des_Chemins_de_Fer_Luxembourgeois) is the national railway company of [Luxembourg](https://en.wikipedia.org/wiki/Luxembourg). This profile adds *CFL*-specific customisations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as cflProfile} from 'hafas-client/p/cfl/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with CFL profile 14 | const client = createClient(cflProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - *CFL*-specific products (such as [*Standseilbahn_Pfaffenthal-Kirchberg*](https://de.wikipedia.org/wiki/Standseilbahn_Pfaffenthal-Kirchberg)) 21 | -------------------------------------------------------------------------------- /p/cmta/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'ioslaskdcndrjcmlsd', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'CMTA', 9 | v: '2', 10 | name: 'CapMetro', 11 | }, 12 | endpoint: 'https://capmetro.hafas.cloud/bin/mgate.exe', 13 | ver: '1.40', 14 | defaultLanguage: 'en', 15 | }; 16 | -------------------------------------------------------------------------------- /p/cmta/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'en-US', 7 | timezone: 'America/Chicago', 8 | 9 | products, 10 | 11 | refreshJourneyUseOutReconL: true, 12 | trip: true, 13 | radar: true, 14 | refreshJourney: true, 15 | reachableFrom: true, 16 | remarks: true, // `.svcResL[0].res.msgL[]` is missing though 🤔 17 | }; 18 | 19 | export { 20 | profile, 21 | }; 22 | -------------------------------------------------------------------------------- /p/cmta/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'bus', 4 | mode: 'bus', 5 | bitmasks: [32], 6 | name: 'MetroBus', 7 | short: 'B', 8 | default: true, 9 | }, 10 | { 11 | id: 'rapid', 12 | mode: 'bus', 13 | bitmasks: [4096], 14 | name: 'MetroRapid', 15 | short: 'R', 16 | default: true, 17 | }, 18 | { 19 | id: 'rail', 20 | mode: 'train', 21 | bitmasks: [8], 22 | name: 'MetroRail', 23 | short: 'M', 24 | default: true, 25 | }, 26 | ]; 27 | 28 | export { 29 | products, 30 | }; 31 | -------------------------------------------------------------------------------- /p/cmta/readme.md: -------------------------------------------------------------------------------- 1 | # CMTA profile for `hafas-client` 2 | 3 | [*Capital Metropolitan Transportation Authority (CMTA)* or *CapMetro*](https://en.wikipedia.org/wiki/Capital_Metropolitan_Transportation_Authority) is a public transportation provider serving [Austin, Texas](https://en.wikipedia.org/wiki/Austin,_Texas) metropolitan area. This profile adds *CMTA*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as cmtaProfile} from 'hafas-client/p/cmta/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with CMTA profile 14 | const client = createClient(cmtaProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *CMTA*-specific products (such as *MetroRapid* and *MetroRail*) 21 | -------------------------------------------------------------------------------- /p/dart/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'XNFGL2aSkxfDeK8N4waOZnsdJ', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'DART', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://dart.hafas.de/bin/mgate.exe', 12 | ver: '1.35', 13 | defaultLanguage: 'en', 14 | }; 15 | -------------------------------------------------------------------------------- /p/dart/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | 3 | const products = [{ 4 | id: 'bus', 5 | mode: 'bus', 6 | bitmasks: [32], 7 | name: 'Bus', 8 | short: 'Bus', 9 | default: true, 10 | }]; 11 | 12 | const profile = { 13 | ...baseProfile, 14 | locale: 'en-US', 15 | timezone: 'America/Chicago', 16 | 17 | products, 18 | 19 | refreshJourneyUseOutReconL: true, 20 | trip: true, 21 | reachableFrom: true, 22 | radar: true, 23 | }; 24 | 25 | export { 26 | profile, 27 | }; 28 | -------------------------------------------------------------------------------- /p/dart/readme.md: -------------------------------------------------------------------------------- 1 | # DART profile for `hafas-client` 2 | 3 | [*Des Moines Area Rapid Transit (DART)*](https://en.wikipedia.org/wiki/Des_Moines_Area_Regional_Transit) is the local transport provider of [Des Moines](https://en.wikipedia.org/wiki/Des_Moines_metropolitan_area), Iowa, USA. This profile adds *DART* support to `hafas-client`. 4 | 5 | *Note:* This profile *does not* support [*Dallas Area Rapid Transit (DART)*](https://de.wikipedia.org/wiki/Verkehrsverbund_Vorarlberg) in [Dallas–Fort Worth](https://en.wikipedia.org/wiki/Dallas–Fort_Worth_metroplex), Texas, USA. 6 | 7 | ## Usage 8 | 9 | ```js 10 | import {createClient} from 'hafas-client' 11 | import {profile as dartProfile} from 'hafas-client/p/dart/index.js' 12 | 13 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 14 | 15 | // create a client with DART profile 16 | const client = createClient(dartProfile, userAgent) 17 | ``` 18 | 19 | Check out the [code examples](example.js). 20 | -------------------------------------------------------------------------------- /p/db-busradar-nrw/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'OGBAqytjHhCvr0J4', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'DB-REGIO', 9 | v: 3000000, 10 | name: 'DB Busradar NRW', 11 | }, 12 | endpoint: 'https://db-regio.hafas.de/bin/mgate.exe', 13 | ext: 'DB.REGIO.1', 14 | ver: '1.45', 15 | defaultLanguage: 'de', 16 | }; 17 | -------------------------------------------------------------------------------- /p/db-busradar-nrw/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as dbbusradarnrwProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(dbbusradarnrwProfile, 'hafas-client-example') 7 | 8 | const hagenBauhaus = '3307002' 9 | const schwerteBahnhof = '3357026' 10 | 11 | let data = await client.locations('Hagen Vorhalle') 12 | // let data = await client.nearby({ 13 | // type: 'location', 14 | // latitude: 51.38, 15 | // longitude: 7.45 16 | // }, {results: 1}) 17 | 18 | // let data = await client.stop(hagenBauhaus) // Hagen Bauhaus 19 | 20 | // let data = await client.departures(hagenBauhaus, {duration: 60}) 21 | // let data = await client.arrivals(hagenBauhaus, {duration: 30, linesOfStops: true}) 22 | 23 | // let data = await client.journeys(hagenBauhaus, schwerteBahnhof, {results: 1}) 24 | // { 25 | // const [journey] = data.journeys 26 | // data = await client.refreshJourney(journey.refreshToken, { 27 | // stopovers: true, 28 | // remarks: true, 29 | // }) 30 | // } 31 | // { 32 | // const [journey] = data.journeys 33 | // const leg = journey.legs[0] 34 | // data = await client.trip(leg.tripId, {polyline: true}) 35 | // } 36 | 37 | // let data = await client.radar({ 38 | // north: 51.5, 39 | // west: 7.2, 40 | // south: 51.2, 41 | // east: 7.8 42 | // }, {results: 10}) 43 | 44 | console.log(inspect(data, {depth: null, colors: true})) 45 | -------------------------------------------------------------------------------- /p/db-busradar-nrw/readme.md: -------------------------------------------------------------------------------- 1 | # DB Busradar NRW profile for `hafas-client` 2 | 3 | [*DB Busradar NRW*](https://www.bahn.de/westfalenbus/view/fahrplan/busradar.shtml) is a mobile application used in [North Rhine-Westphalia](https://en.wikipedia.org/wiki/North_Rhine-Westphalia). 4 | It shows realtime locations and arrival/departure information for vehicles operated by bus companies which are part of [DB Regio Bus](https://www.dbregio.de/db_regio/view/wir/bus.shtml) in NRW, namely: 5 | - [BVR Busverkehr Rheinland GmbH](https://www.rheinlandbus.de/) (DB Rheinlandbus) 6 | - [WB Westfalen Bus GmbH](https://www.westfalenbus.de/) (DB Westfalenbus) 7 | - [BVO Busverkehr Ostwestfalen GmbH](https://www.ostwestfalen-lippe-bus.de) (DB Ostwestfalen-Lippe-Bus) 8 | 9 | This profile adapts `hafas-client` to the HAFAS endpoint used by the application. 10 | 11 | ## Usage 12 | 13 | ```js 14 | import {createClient} from 'hafas-client' 15 | import {profile as dbbusradarnrwProfile} from 'hafas-client/p/db-busradar-nrw/index.js' 16 | 17 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 18 | 19 | // create a client with DB Busradar NRW profile 20 | const client = createClient(dbbusradarnrwProfile, userAgent) 21 | ``` 22 | -------------------------------------------------------------------------------- /p/db/ageGroup.js: -------------------------------------------------------------------------------- 1 | const ageGroup = { 2 | BABY: 'B', 3 | CHILD: 'K', 4 | YOUNG: 'Y', 5 | ADULT: 'E', 6 | SENIOR: 'S', 7 | upperBoundOf: { 8 | BABY: 6, 9 | CHILD: 15, 10 | YOUNG: 27, 11 | ADULT: 65, 12 | SENIOR: Infinity, 13 | }, 14 | }; 15 | 16 | const ageGroupFromAge = (age) => { 17 | const {upperBoundOf} = ageGroup; 18 | if (age < upperBoundOf.BABY) { 19 | return ageGroup.BABY; 20 | } 21 | if (age < upperBoundOf.CHILD) { 22 | return ageGroup.CHILD; 23 | } 24 | if (age < upperBoundOf.YOUNG) { 25 | return ageGroup.YOUNG; 26 | } 27 | if (age < upperBoundOf.ADULT) { 28 | return ageGroup.ADULT; 29 | } 30 | if (age < upperBoundOf.SENIOR) { 31 | return ageGroup.SENIOR; 32 | } 33 | throw new TypeError(`Invalid age '${age}'`); 34 | }; 35 | 36 | export { 37 | ageGroup, 38 | ageGroupFromAge, 39 | }; 40 | -------------------------------------------------------------------------------- /p/db/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'n91dB8Z77MLdoR0K', 5 | }, 6 | salt: '6264493855566A34304B356676787766', 7 | client: { 8 | type: 'AND', 9 | id: 'DB', 10 | v: 21120000, 11 | name: 'DB Navigator', 12 | }, 13 | endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe', 14 | ext: 'DB.R22.04.a', 15 | ver: '1.78', 16 | defaultLanguage: 'en', 17 | }; 18 | -------------------------------------------------------------------------------- /p/db/routing-modes.js: -------------------------------------------------------------------------------- 1 | // see https://pastebin.com/qZ9WS3Cx 2 | const routingModes = { 3 | OFF: 'OFF', 4 | INFOS: 'INFOS', 5 | FULL: 'FULL', 6 | REALTIME: 'REALTIME', 7 | SERVER_DEFAULT: 'SERVER_DEFAULT', 8 | HYBRID: 'HYBRID', 9 | }; 10 | 11 | export { 12 | routingModes, 13 | }; 14 | -------------------------------------------------------------------------------- /p/insa/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'nasa-apps', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'NASA', 9 | v: '4000200', 10 | name: 'nasaPROD', 11 | }, 12 | endpoint: 'https://reiseauskunft.insa.de/bin/mgate.exe', 13 | ver: '1.44', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/insa/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products: products, 10 | 11 | trip: true, 12 | radar: true, 13 | refreshJourneyUseOutReconL: true, 14 | reachableFrom: true, 15 | }; 16 | 17 | export { 18 | profile, 19 | }; 20 | -------------------------------------------------------------------------------- /p/insa/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'nationalExpress', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'InterCityExpress', 7 | short: 'ICE', 8 | default: true, 9 | }, 10 | { 11 | id: 'national', 12 | mode: 'train', 13 | bitmasks: [2], 14 | name: 'InterCity & EuroCity', 15 | short: 'IC/EC', 16 | default: true, 17 | }, 18 | { 19 | id: 'regional', 20 | mode: 'train', 21 | bitmasks: [8], 22 | name: 'RegionalExpress & RegionalBahn', 23 | short: 'RE/RB', 24 | default: true, 25 | }, 26 | { 27 | id: 'suburban', 28 | mode: 'train', 29 | bitmasks: [16], 30 | name: 'S-Bahn', 31 | short: 'S', 32 | default: true, 33 | }, 34 | { 35 | id: 'tram', 36 | mode: 'train', 37 | bitmasks: [32], 38 | name: 'Tram', 39 | short: 'T', 40 | default: true, 41 | }, 42 | { 43 | id: 'bus', 44 | mode: 'bus', 45 | bitmasks: [64, 128], 46 | name: 'Bus', 47 | short: 'B', 48 | default: true, 49 | }, 50 | { 51 | id: 'tourismTrain', 52 | mode: 'train', 53 | bitmasks: [256], 54 | name: 'Tourism Train', 55 | short: 'TT', 56 | default: true, 57 | }, 58 | ]; 59 | 60 | export { 61 | products, 62 | }; 63 | -------------------------------------------------------------------------------- /p/insa/readme.md: -------------------------------------------------------------------------------- 1 | # INSA profile for `hafas-client` 2 | 3 | The [Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt) offers [Informationssystem Nahverkehr Sachsen-Anhalt (INSA)](https://insa.de) to distribute their public transport data. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as insaProfile} from 'hafas-client/p/insa/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with INSA profile 14 | const client = createClient(insaProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *INSA*-specific products (such as *Tourism Train*) 21 | -------------------------------------------------------------------------------- /p/invg/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'GITvwi3BGOmTQ2a5', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'INVG', 9 | v: '1040000', 10 | name: 'invgPROD-APPSTORE-LIVE', 11 | }, 12 | endpoint: 'https://fpa.invg.de/bin/mgate.exe', 13 | ver: '1.39', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/invg/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as invgProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(invgProfile, 'hafas-client-example') 7 | 8 | const ingolstadtHbf = '8000183' 9 | const audiParkplatz = '84999' 10 | 11 | let data = await client.locations('tillystr 1', {results: 2}) 12 | // let data = await client.nearby({ 13 | // type: 'location', 14 | // latitude: 48.74453, 15 | // longitude: 11.43733 16 | // }, {distance: 200}) 17 | // // todo: `reachableFrom` with `Ingolstadt, Tillystraße 1` 48.745769 | 11.432814 18 | 19 | // let data = await client.stop(audiParkplatz) 20 | 21 | // let data = await client.departures(ingolstadtHbf, {duration: 5}) 22 | // let data = await client.arrivals(ingolstadtHbf, {duration: 10, stationLines: true}) 23 | 24 | // let data = await client.journeys(ingolstadtHbf, audiParkplatz, {results: 1}) 25 | // { 26 | // const [journey] = data.journeys 27 | // data = await client.refreshJourney(journey.refreshToken, { 28 | // stopovers: true, 29 | // remarks: true, 30 | // }) 31 | // } 32 | // { 33 | // const [journey] = data.journeys 34 | // const leg = journey.legs[0] 35 | // data = await client.trip(leg.tripId, {polyline: true}) 36 | // } 37 | 38 | // let data = await client.radar({ 39 | // north: 48.74453, 40 | // west: 11.42733, 41 | // south: 48.73453, 42 | // east: 11.43733 43 | // }, {results: 10}) 44 | 45 | console.log(inspect(data, {depth: null, colors: true})) 46 | -------------------------------------------------------------------------------- /p/invg/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products, 10 | 11 | refreshJourneyUseOutReconL: true, 12 | trip: true, 13 | radar: true, 14 | refreshJourney: true, 15 | }; 16 | 17 | export { 18 | profile, 19 | }; 20 | -------------------------------------------------------------------------------- /p/invg/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | // https://github.com/public-transport/hafas-client/issues/93#issuecomment-437868265 3 | { 4 | id: 'bus', 5 | mode: 'bus', 6 | bitmasks: [1, 16], 7 | name: 'Bus', 8 | short: 'Bus', 9 | default: true, // the other `bus` has `false` 10 | }, 11 | { 12 | id: 'express-train', 13 | mode: 'train', 14 | bitmasks: [2], 15 | name: 'High-speed train', 16 | short: 'Train', 17 | default: false, 18 | }, 19 | { 20 | id: 'regional-train', 21 | mode: 'train', 22 | bitmasks: [4], 23 | name: 'Regional train', 24 | short: 'Train', 25 | default: false, 26 | }, 27 | { 28 | id: 'local-train', 29 | mode: 'train', 30 | bitmasks: [8], 31 | name: 'Nahverkehrszug', 32 | short: 'Zug', 33 | default: true, 34 | }, 35 | { 36 | id: 'ferry', 37 | mode: 'watercraft', 38 | bitmasks: [32], 39 | name: 'Ferry', 40 | short: 'Ferry', 41 | default: false, 42 | }, 43 | { 44 | id: 'subway', 45 | mode: 'train', 46 | bitmasks: [64], 47 | name: 'Subway', 48 | short: 'Subway', 49 | default: false, 50 | }, 51 | { 52 | id: 'tram', 53 | mode: 'train', 54 | bitmasks: [128], 55 | name: 'Tram', 56 | short: 'Tram', 57 | default: false, 58 | }, 59 | { 60 | id: 'on-demand', 61 | mode: 'bus', // todo: correct? 62 | bitmasks: [256], 63 | name: 'On-demand traffic', 64 | short: 'on demand', 65 | default: false, 66 | }, 67 | ]; 68 | 69 | export { 70 | products, 71 | }; 72 | -------------------------------------------------------------------------------- /p/invg/readme.md: -------------------------------------------------------------------------------- 1 | # INVG profile for `hafas-client` 2 | 3 | [*Ingolstädter Verkehrsgesellschaft (INVG)*](https://de.wikipedia.org/wiki/Ingolstädter_Verkehrsgesellschaft) is a public transportation provider serving [Ingolstadt, Germany](https://en.wikipedia.org/wiki/Ingolstadt). This profile adds *INVG*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as invgProfile} from 'hafas-client/p/invg/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with INVG profile 14 | const client = createClient(invgProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *INVG*-specific products 21 | -------------------------------------------------------------------------------- /p/irish-rail/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'P9bplgVCGnozdgQE', 5 | }, 6 | client: { 7 | type: 'IPA', 8 | id: 'IRISHRAIL', 9 | v: '4000100', 10 | name: 'IrishRailPROD-APPSTORE', 11 | }, 12 | endpoint: 'https://journeyplanner.irishrail.ie/bin/mgate.exe', 13 | ver: '1.33', 14 | defaultLanguage: 'en', 15 | }; 16 | -------------------------------------------------------------------------------- /p/irish-rail/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as irishProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(irishProfile, 'hafas-client example') 7 | 8 | const dublin = '9909002' 9 | const belfastCentral = '9990840' 10 | 11 | let data = await client.locations('Dublin', {results: 2}) 12 | // let data = await client.locations('Hochschule Dublin', { 13 | // poi: true, 14 | // addressses: false, 15 | // fuzzy: false, 16 | // }) 17 | // let data = await client.nearby({ 18 | // type: 'location', 19 | // latitude: 53.353, 20 | // longitude: -6.247 21 | // }, {distance: 200}) 22 | 23 | // let data = await client.stop(dublin) // Dublin 24 | 25 | // let data = await client.departures(dublin, {duration: 5}) 26 | // let data = await client.arrivals(dublin, {duration: 10, linesOfStops: true}) 27 | 28 | // let data = await client.journeys(dublin, belfastCentral, {results: 1}) 29 | // { 30 | // const [journey] = data.journeys 31 | // const leg = journey.legs[0] 32 | // data = await client.trip(leg.tripId, {polyline: true}) 33 | // } 34 | 35 | // let data = await client.radar({ 36 | // north: 53.35, 37 | // west: -6.245, 38 | // south: 53.34, 39 | // east: -6.244 40 | // }, {results: 10}) 41 | 42 | console.log(inspect(data, {depth: null, colors: true})) 43 | -------------------------------------------------------------------------------- /p/irish-rail/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'en-IE', 7 | timezone: 'Europe/Dublin', 8 | defaultLanguage: 'ga', 9 | salt: Buffer.from('i5s7m3q9z6b4k1c2', 'utf8'), 10 | addMicMac: true, 11 | 12 | products: products, 13 | 14 | refreshJourney: false, // fails with `CGI_READ_FAILED` 15 | trip: true, 16 | radar: true, 17 | }; 18 | 19 | export { 20 | profile, 21 | }; 22 | -------------------------------------------------------------------------------- /p/irish-rail/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'national-train', 4 | mode: 'train', 5 | bitmasks: [2], 6 | name: 'InterCity', 7 | short: 'IC', 8 | default: true, 9 | }, 10 | // todo: 4 11 | { 12 | id: 'local-train', 13 | mode: 'train', 14 | bitmasks: [8], 15 | name: 'Commuter', 16 | short: 'Commuter', 17 | default: true, 18 | }, 19 | { 20 | id: 'suburban', 21 | mode: 'train', 22 | bitmasks: [16], 23 | name: 'Dublin Area Rapid Transit', 24 | short: 'DART', 25 | default: true, 26 | }, 27 | // todo: 32 28 | { 29 | id: 'luas', 30 | mode: 'train', 31 | bitmasks: [64], 32 | name: 'LUAS Tram', 33 | short: 'LUAS', 34 | default: true, 35 | }, 36 | ]; 37 | 38 | export { 39 | products, 40 | }; 41 | -------------------------------------------------------------------------------- /p/irish-rail/readme.md: -------------------------------------------------------------------------------- 1 | # Irish Rail profile for `hafas-client` 2 | 3 | The [*Iarnród Éireann* (Irish Rail)](https://en.wikipedia.org/wiki/Iarnród_Éireann) is the national railway company of Ireland. This profile adds *Iarnród Éireann*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as irishProfile} from 'hafas-client/p/irish-rail/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with Irish Rail profile 14 | const client = createClient(irishProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *Irish Rail*-specific products (such as *LUAS* or *DART*) 21 | -------------------------------------------------------------------------------- /p/ivb/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'wf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VAO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://fahrplan.ivb.at/bin/mgate.exe', 12 | defaultLanguage: 'de', 13 | }; 14 | -------------------------------------------------------------------------------- /p/ivb/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as ivbProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(ivbProfile, 'hafas-client example') 7 | 8 | const innsbruckGriesauweg = '476162400' 9 | const völsWest = '476431800' 10 | const lönsstr9 = { 11 | type: 'location', 12 | id: '980076175', 13 | address: 'Lönsstraße 9, 6020 Innsbruck', 14 | latitude: 47.262765, 15 | longitude: 11.419851, 16 | } 17 | 18 | let data = await client.locations('griesauweg', {results: 3}) 19 | // let data = await client.nearby(lönsstr9, {distance: 1000}) 20 | // let data = await client.reachableFrom(lönsstr9, { 21 | // maxDuration: 30, 22 | // }) 23 | 24 | // let data = await client.stop(innsbruckGriesauweg, {linesOfStops: true}) 25 | 26 | // let data = await client.departures(innsbruckGriesauweg, {duration: 1}) 27 | // let data = await client.arrivals(innsbruckGriesauweg, {duration: 10, linesOfStops: true}) 28 | 29 | // let data = await client.journeys(innsbruckGriesauweg, völsWest, { 30 | // results: 1, stopovers: true, 31 | // }) 32 | // { 33 | // const [journey] = data.journeys 34 | // data = await client.refreshJourney(journey.refreshToken, { 35 | // stopovers: true, 36 | // remarks: true, 37 | // }) 38 | // } 39 | // { 40 | // const [journey] = data.journeys 41 | // const leg = journey.legs[0] 42 | // data = await client.trip(leg.tripId, {polyline: true}) 43 | // } 44 | 45 | console.log(inspect(data, {depth: null, colors: true})) 46 | -------------------------------------------------------------------------------- /p/ivb/readme.md: -------------------------------------------------------------------------------- 1 | # IVB profile for `hafas-client` 2 | 3 | [*Innsbrucker Verkehrsbetriebe (IVB)*](https://de.wikipedia.org/wiki/Innsbrucker_Verkehrsbetriebe_und_Stubaitalbahn) is the local transport provider of [Innsbruck](https://en.wikipedia.org/wiki/Innsbruck). This profile adds *IVB* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as ivbProfile} from 'hafas-client/p/ivb/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with IVB profile 14 | const client = createClient(ivbProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/kvb/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'Rt6foY5zcTTRXMQs', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'HAFAS', 9 | name: 'webapp', 10 | l: 'vs_webapp', 11 | }, 12 | endpoint: 'https://auskunft.kvb.koeln/gate', 13 | ver: '1.42', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/kvb/digicert-global-root-g2.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH 5 | MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI 9 | 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx 10 | 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ 11 | q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz 12 | tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ 13 | vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP 14 | BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV 15 | 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY 16 | 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 17 | NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG 18 | Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 19 | 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe 20 | pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl 21 | MrY= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /p/kvb/readme.md: -------------------------------------------------------------------------------- 1 | # KVB profile for `hafas-client` 2 | 3 | [*Kölner Verkehrs-Betriebe (KVB)*](https://de.wikipedia.org/wiki/Kölner_Verkehrs-Betriebe) is the local transport provider of [Cologne](https://en.wikipedia.org/wiki/Cologne). This profile adds *KVB* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as kvbProfile} from 'hafas-client/p/kvb/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with KVB profile 14 | const client = createClient(kvbProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/mobil-nrw/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'Kdf0LNRWYg5k3499', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'DB-REGIO-NRW', 9 | v: '6000300', 10 | name: 'NRW', 11 | }, 12 | endpoint: 'https://nrw.hafas.de/bin/mgate.exe', 13 | ver: '1.34', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/mobil-nrw/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products, 10 | 11 | trip: true, 12 | radar: true, 13 | reachableFrom: true, 14 | refreshJourneyUseOutReconL: true, 15 | remarks: true, 16 | }; 17 | 18 | export { 19 | profile, 20 | }; 21 | -------------------------------------------------------------------------------- /p/mobil-nrw/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'regional-train', 4 | mode: 'train', 5 | bitmasks: [8], 6 | // todo: specify explicitly which 7 | name: 'regional train', 8 | short: 'regional train', 9 | default: true, 10 | }, 11 | { 12 | id: 'urban-train', 13 | mode: 'train', 14 | bitmasks: [16], 15 | name: 'urban train', 16 | short: 'urban train', 17 | default: true, 18 | }, 19 | { 20 | id: 'subway', 21 | mode: 'train', 22 | bitmasks: [128], 23 | name: 'subway', 24 | short: 'subway', 25 | default: true, 26 | }, 27 | { 28 | id: 'tram', 29 | mode: 'train', 30 | bitmasks: [256], 31 | name: 'tram', 32 | short: 'tram', 33 | default: true, 34 | }, 35 | { 36 | id: 'bus', 37 | mode: 'bus', 38 | bitmasks: [32], 39 | name: 'bus', 40 | short: 'bus', 41 | default: true, 42 | }, 43 | { 44 | id: 'dial-a-ride', 45 | mode: 'taxi', 46 | bitmasks: [512], 47 | name: 'dial-a-ride', 48 | short: 'dial-a-ride', 49 | default: true, 50 | }, 51 | { 52 | id: 'long-distance-train', 53 | mode: 'train', 54 | bitmasks: [4], 55 | // todo: specify explicitly which 56 | name: 'long-distance train', 57 | short: 'long-distance train', 58 | default: true, 59 | }, 60 | { 61 | id: 'express-train', 62 | mode: 'train', 63 | bitmasks: [1], 64 | name: 'ICE', 65 | short: 'ICE', 66 | default: true, 67 | }, 68 | { 69 | id: 'ec-ic', 70 | mode: 'train', 71 | bitmasks: [2], 72 | name: 'EC/IC', 73 | short: 'EC/IC', 74 | default: true, 75 | }, 76 | ]; 77 | 78 | export { 79 | products, 80 | }; 81 | -------------------------------------------------------------------------------- /p/mobil-nrw/readme.md: -------------------------------------------------------------------------------- 1 | # *mobil.nrw* profile for `hafas-client` 2 | 3 | [*mobil.nrw*](https://www.mobil.nrw) is the name of the travel planning service of the [*NRW-Tarif*](https://de.wikipedia.org/wiki/NRW-Tarif). This profile adds *mobil.nrw*-specific customisations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as mobilNrwProfile} from 'hafas-client/p/mobil-nrw/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with mobil.nrw profile 14 | const client = createClient(mobilNrwProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - *mobil.nrw*-specific products 21 | -------------------------------------------------------------------------------- /p/mobiliteit-lu/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'SkC81GuwuzL4e0', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'MMILUX', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://cdt.hafas.de/bin/mgate.exe', 12 | ver: '1.43', 13 | defaultLanguage: 'de', 14 | }; 15 | -------------------------------------------------------------------------------- /p/mobiliteit-lu/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-LU', 7 | timezone: 'Europe/Luxembourg', 8 | 9 | products: products, 10 | 11 | trip: true, 12 | radar: true, 13 | reachableFrom: true, 14 | 15 | refreshJourneyUseOutReconL: true, 16 | }; 17 | 18 | export { 19 | profile, 20 | }; 21 | -------------------------------------------------------------------------------- /p/mobiliteit-lu/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'express-train', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'local train (TGV/ICE)', 7 | short: 'TGV/ICE', 8 | default: true, 9 | }, 10 | { 11 | id: 'national-train', 12 | mode: 'train', 13 | bitmasks: [2, 4], 14 | name: 'national train (IC/RE/IRE)', 15 | short: 'IC/RE/IRE', 16 | default: true, 17 | }, 18 | { 19 | id: 'local-train', 20 | mode: 'train', 21 | bitmasks: [8], 22 | name: 'local train (RB/TER)', 23 | short: 'RB/TER', 24 | default: true, 25 | }, 26 | { 27 | id: 'bus', 28 | mode: 'bus', 29 | bitmasks: [32], 30 | name: 'Bus', 31 | short: 'Bus', 32 | default: true, 33 | }, 34 | { 35 | id: 'tram', 36 | mode: 'train', 37 | bitmasks: [256], 38 | name: 'Tram', 39 | short: 'Tram', 40 | default: true, 41 | }, 42 | ]; 43 | 44 | export { 45 | products, 46 | }; 47 | -------------------------------------------------------------------------------- /p/mobiliteit-lu/readme.md: -------------------------------------------------------------------------------- 1 | # mobiliteit.lu profile for `hafas-client` 2 | 3 | [*mobiliteit.lu*](https://www.mobiliteit.lu) provides public transport data for Luxembourg. This profile adds *mobiliteit.lu*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as mobiliteitProfile} from 'hafas-client/p/mobiliteit-lu/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with mobiliteit.lu profile 14 | const client = createClient(mobiliteitProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *mobiliteit.lu*-specific products 21 | -------------------------------------------------------------------------------- /p/nahsh/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'r0Ot9FLFNAFxijLW', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'NAHSH', 9 | v: '3000700', 10 | name: 'NAHSHPROD', 11 | }, 12 | endpoint: 'https://nah.sh.hafas.de/bin/mgate.exe', 13 | ver: '1.30', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/nahsh/readme.md: -------------------------------------------------------------------------------- 1 | # NAH.SH profile for `hafas-client` 2 | 3 | [*Nahverkehrsverbund Schleswig-Holstein (NAH.SH)*](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) is the transportation authority for regional transport in [Schleswig-Holstein](https://en.wikipedia.org/wiki/Schleswig-Holstein). This profile adds *NAH.SH*-specific customizations to `hafas-client`. Consider using [`nahsh-hafas`](https://github.com/juliuste/nahsh-hafas), to always get the customized client right away. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as nahshProfile} from 'hafas-client/p/nahsh/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with NAH.SH profile 14 | const client = createClient(nahshProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *NAH.SH*-specific products 21 | -------------------------------------------------------------------------------- /p/nvv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'Kt8eNOH7qjVeSxNA', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'NVV', 9 | v: '5000300', 10 | name: 'NVVMobilPROD_APPSTORE', 11 | }, 12 | endpoint: 'https://auskunft.nvv.de/auskunft/bin/app/mgate.exe', 13 | ext: 'NVV.6.0', 14 | ver: '1.45', 15 | defaultLanguage: 'de', 16 | }; 17 | -------------------------------------------------------------------------------- /p/nvv/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products: products, 10 | 11 | refreshJourneyUseOutReconL: true, 12 | trip: true, 13 | radar: true, 14 | reachableFrom: true, 15 | }; 16 | 17 | export { 18 | profile, 19 | }; 20 | -------------------------------------------------------------------------------- /p/nvv/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | // todo: what is `256`? 3 | { 4 | id: 'express', 5 | mode: 'train', 6 | bitmasks: [1], 7 | name: 'InterCityExpress', 8 | short: 'ICE', 9 | default: true, 10 | }, 11 | { 12 | id: 'national', 13 | mode: 'train', 14 | bitmasks: [2], 15 | name: 'EuroCity/InterCity', 16 | short: 'EC/IC', 17 | default: true, 18 | }, 19 | { 20 | id: 'regional', 21 | mode: 'train', 22 | bitmasks: [4], 23 | name: 'Regionalzug', 24 | short: 'RE/RB', 25 | default: true, 26 | }, 27 | { 28 | id: 'regiotram', 29 | mode: 'train', 30 | bitmasks: [1024, 16, 8], // it is `1048` actually 31 | name: 'RegioTram', 32 | short: 'RegioTram', 33 | default: true, 34 | }, 35 | { 36 | id: 'tram', 37 | mode: 'train', 38 | bitmasks: [4, 32], 39 | name: 'Tram', 40 | short: 'Tram', 41 | default: true, 42 | }, 43 | { 44 | id: 'bus', 45 | mode: 'bus', 46 | bitmasks: [128, 64], // it is `192` actually 47 | name: 'Bus', 48 | short: 'Bus', 49 | default: true, 50 | }, 51 | { 52 | id: 'on-call', 53 | mode: 'taxi', // todo: or `bus`? 54 | bitmasks: [512], 55 | name: 'AnrufSammelTaxi', 56 | short: 'Sammeltaxi', 57 | default: true, 58 | }, 59 | ]; 60 | 61 | export { 62 | products, 63 | }; 64 | -------------------------------------------------------------------------------- /p/nvv/readme.md: -------------------------------------------------------------------------------- 1 | # NVV profile for `hafas-client` 2 | 3 | [*Nordhessischer Verkehrsverbund (NVV)*](https://en.wikipedia.org/wiki/Nordhessischer_Verkehrsverbund) is a local transport association in [Hesse](https://en.wikipedia.org/wiki/Hesse). This profile adds *NVV*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as nvvProfile} from 'hafas-client/p/nvv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with NVV profile 14 | const client = createClient(nvvProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *NVV*-specific products (such as *RegioTram*) 21 | -------------------------------------------------------------------------------- /p/oebb/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'OWDL4fE4ixNiPBBm', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'OEBB', 9 | v: '6030600', 10 | name: 'oebbPROD-ADHOC', 11 | }, 12 | endpoint: 'https://fahrplan.oebb.at/bin/mgate.exe', 13 | ver: '1.45', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/oebb/readme.md: -------------------------------------------------------------------------------- 1 | # ÖBB profile for `hafas-client` 2 | 3 | [*Österreichische Bundesbahnen (ÖBB)*](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) is the largest Austrian long-distance public transport company. This profile adds *ÖBB*-specific customizations to `hafas-client`. Consider using [`oebb-hafas`](https://github.com/juliuste/oebb-hafas#oebb-hafas), to always get the customized client right away. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as oebbProfile} from 'hafas-client/p/oebb/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with ÖBB profile 14 | const client = createClient(oebbProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *ÖBB*-specific products (such as *RailJet*) 21 | - parses invalid empty stations from the API as [`location`](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#location-objects)s 22 | -------------------------------------------------------------------------------- /p/ooevv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'and20201hf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'VAO', 9 | }, 10 | endpoint: 'https://app.verkehrsauskunft.at/bin/mgate.exe', 11 | ext: 'VAO.11', 12 | ver: '1.27', 13 | defaultLanguage: 'de', 14 | }; 15 | -------------------------------------------------------------------------------- /p/ooevv/readme.md: -------------------------------------------------------------------------------- 1 | # OÖVV profile for `hafas-client` 2 | 3 | [*Oberösterreichischer Verkehrsverbund (OÖVV)*](https://de.wikipedia.org/wiki/Oberösterreichischer_Verkehrsverbund) is the local transport provider of [Upper Austria](https://en.wikipedia.org/wiki/Upper_Austria). This profile adds *OÖVV* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as ooevvProfile} from 'hafas-client/p/ooevv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with OÖVV profile 14 | const client = createClient(ooevvProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/pkp/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'DrxJYtYZQpEBCtcb', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'HAFAS', 9 | }, 10 | endpoint: 'https://mobil.rozklad-pkp.pl:8019/bin/mgate.exe', 11 | ver: '1.21', 12 | defaultLanguage: 'pl', 13 | }; 14 | -------------------------------------------------------------------------------- /p/pkp/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as pkpProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(pkpProfile, 'hafas-client-example') 7 | 8 | const wrocławGł = '5100069' 9 | const krakówGł = '5100028' 10 | 11 | let data = await client.locations('kraków', {results: 2}) 12 | // let data = await client.stop(krakówGł, {linesOfStops: true}) 13 | // let data = await client.nearby({ 14 | // type: 'location', 15 | // latitude: 50.067192, 16 | // longitude: 19.947423 17 | // }, {distance: 60}) 18 | // let data = await client.radar({ 19 | // north: 50.2, 20 | // west: 19.8, 21 | // south: 49.9, 22 | // east: 20.1 23 | // }, {results: 10}) 24 | // let data = await client.reachableFrom({ 25 | // type: 'location', 26 | // address: 'Bydgoszcz, Dworcowa 100', 27 | // latitude: 53.1336648, 28 | // longitude: 17.9908571 29 | // }, { 30 | // when: new Date(), 31 | // maxDuration: 20 32 | // }) 33 | 34 | // let data = await client.departures(krakówGł, {duration: 10}) 35 | // let data = await client.arrivals(krakówGł, {duration: 10, linesOfStops: true}) 36 | 37 | // let data = await client.journeys(krakówGł, wrocławGł, { 38 | // results: 1, 39 | // polylines: true, 40 | // }) 41 | // { 42 | // const [journey] = data.journeys 43 | // const leg = journey.legs[0] 44 | // data = await client.trip(leg.tripId, {polyline: true}) 45 | // } 46 | 47 | console.log(inspect(data, {depth: null, colors: true})) 48 | -------------------------------------------------------------------------------- /p/pkp/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'high-speed-train', 4 | mode: 'train', 5 | bitmasks: [1, 2], 6 | name: 'ExpressInterCity & ExpressInterCity Premium & InterCityExpress', 7 | short: 'EIC/EIP/ICE', 8 | default: true, 9 | }, 10 | { 11 | id: 'long-distance-train', 12 | mode: 'train', 13 | bitmasks: [4], 14 | name: 'InterCity & Twoje Linie Kolejowe & EuroCity & EuroNight', 15 | short: 'IC/TLK/EC/EN', 16 | default: true, 17 | }, 18 | { 19 | id: 'regional-train', 20 | mode: 'train', 21 | bitmasks: [8], 22 | name: 'Regional', 23 | short: 'R', 24 | default: true, 25 | }, 26 | { 27 | id: 'bus', 28 | mode: 'bus', 29 | bitmasks: [32], 30 | name: 'Bus', 31 | short: 'B', 32 | default: true, 33 | }, 34 | ]; 35 | 36 | export { 37 | products, 38 | }; 39 | -------------------------------------------------------------------------------- /p/pkp/readme.md: -------------------------------------------------------------------------------- 1 | # PKP profile for `hafas-client` 2 | 3 | [*Polskie Koleje Państwowe (PKP)*](https://en.wikipedia.org/wiki/Polish_State_Railways) is the major national transport provider in Poland. This profile adds *PKP*-specific customizations to `hafas-client`. 4 | 5 | > [!CAUTION] 6 | > Note that usage of the endpoint might be subject to [terms of service](http://regulamin.rozklad-pkp.pl). Please clarify first if you're allowed to access it. 7 | 8 | > [!TIP] 9 | > The endpoint applies IP-based Geoblocking with some false positives, so you might have additional steps to go through in order to access it. 10 | 11 | ## Usage 12 | 13 | ```js 14 | import {createClient} from 'hafas-client' 15 | import {profile as pkpProfile} from 'hafas-client/p/pkp/index.js' 16 | 17 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 18 | 19 | // create a client with PKP profile 20 | const client = createClient(pkpProfile, userAgent) 21 | ``` 22 | 23 | 24 | ## Customisations 25 | 26 | - parses *PKP*-specific products (such as *TLK*) 27 | -------------------------------------------------------------------------------- /p/rejseplanen/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'irkmpm9mdznstenr-android', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'DK', 9 | }, 10 | endpoint: 'https://mobilapps.rejseplanen.dk/bin/iphone.exe', 11 | ext: 'DK.9', 12 | ver: '1.43', 13 | defaultLanguage: 'dk', 14 | }; 15 | -------------------------------------------------------------------------------- /p/rejseplanen/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as rejseplanenProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(rejseplanenProfile, 'hafas-client-example') 7 | 8 | const københavnCentral = '8600626' 9 | const aalborg = '8600020' 10 | 11 | let data = await client.locations('Københaven', {results: 2}) 12 | // let data = await client.nearby({ 13 | // type: 'location', 14 | // latitude: 55.673, 15 | // longitude: 12.566 16 | // }, {distance: 200}) 17 | 18 | // let data = await client.stop(københavnCentral) // København Central 19 | 20 | // let data = await client.departures(københavnCentral, {duration: 5}) 21 | 22 | // let data = await client.journeys(københavnCentral, aalborg, {results: 1}) 23 | // { 24 | // const [journey] = data.journeys 25 | // const leg = journey.legs[0] 26 | // data = await client.trip(leg.tripId, leg.line.name) 27 | // } 28 | 29 | // let data = await client.radar({ 30 | // north: 55.673, 31 | // west: 12.566, 32 | // south: 55.672, 33 | // east: 12.567 34 | // }, {results: 10}) 35 | 36 | console.log(inspect(data, {depth: null, colors: true})) 37 | -------------------------------------------------------------------------------- /p/rejseplanen/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'da-DK', 7 | timezone: 'Europe/Copenhagen', 8 | 9 | products: products, 10 | 11 | refreshJourneyUseOutReconL: true, 12 | trip: true, 13 | radar: true, 14 | }; 15 | 16 | export { 17 | profile, 18 | }; 19 | -------------------------------------------------------------------------------- /p/rejseplanen/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'national-train', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'InterCity', 7 | short: 'IC', 8 | default: true, 9 | }, 10 | { 11 | id: 'national-train-2', 12 | mode: 'train', 13 | bitmasks: [2], 14 | name: 'ICL', // todo: find proper name 15 | short: 'ICL', 16 | default: true, 17 | }, 18 | { 19 | id: 'local-train', 20 | mode: 'train', 21 | bitmasks: [4], 22 | name: 'Regional', 23 | short: 'RE', 24 | default: true, 25 | }, 26 | { 27 | id: 'o', 28 | mode: 'train', // todo: correct? 29 | bitmasks: [8], 30 | name: 'Ø', // todo: find proper name 31 | short: 'Ø', 32 | default: true, 33 | }, 34 | { 35 | id: 's-tog', 36 | mode: 'train', 37 | bitmasks: [16], 38 | name: 'S-Tog A/B/Bx/C/E/F/H', 39 | short: 'S', 40 | default: true, 41 | }, 42 | ]; 43 | 44 | export { 45 | products, 46 | }; 47 | -------------------------------------------------------------------------------- /p/rejseplanen/readme.md: -------------------------------------------------------------------------------- 1 | # Rejseplanen profile for `hafas-client` 2 | 3 | [*Rejseplanen*](https://da.wikipedia.org/wiki/Rejseplanen) is a Danish website for finding public transport connections throughout Denmark. This profile adds *Rejseplanen*-specific customisations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as rejseplanenProfile} from 'hafas-client/p/rejseplanen/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with Rejseplanen profile 14 | const client = createClient(rejseplanenProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses Denmark-specific products (such as *S-Tog*) 21 | -------------------------------------------------------------------------------- /p/rmv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'x0k4ZR33ICN9CWmj', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'RMV', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://www.rmv.de/auskunft/bin/jp/mgate.exe', 12 | ext: 'RMV.1', 13 | ver: '1.44', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/rmv/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products, 10 | 11 | refreshJourneyUseOutReconL: true, 12 | trip: true, 13 | radar: true, 14 | refreshJourney: true, 15 | reachableFrom: true, 16 | }; 17 | 18 | export { 19 | profile, 20 | }; 21 | -------------------------------------------------------------------------------- /p/rmv/readme.md: -------------------------------------------------------------------------------- 1 | # RMV profile for `hafas-client` 2 | 3 | [*Rhein-Main-Verkehrsverbund (RMV)*](https://en.wikipedia.org/wiki/Rhein-Main-Verkehrsverbund) is a public transport authority in [Hesse](https://en.wikipedia.org/wiki/Hesse)/[Rhineland-Palatinate](https://en.wikipedia.org/wiki/Rhineland-Palatinate). This profile adds *RMV*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as rmvProfile} from 'hafas-client/p/rmv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with RMV profile 14 | const client = createClient(rmvProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *RMV*-specific products 21 | -------------------------------------------------------------------------------- /p/rsag/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'tF5JTs25rzUhGrrl', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'RSAG', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://fahrplan.rsag-online.de/bin/mgate.exe', 12 | ext: 'VBN.2', 13 | ver: '1.42', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/rsag/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products, 10 | 11 | trip: true, 12 | radar: true, 13 | reachableFrom: true, 14 | refreshJourneyUseOutReconL: true, 15 | }; 16 | 17 | export { 18 | profile, 19 | }; 20 | -------------------------------------------------------------------------------- /p/rsag/readme.md: -------------------------------------------------------------------------------- 1 | # RSAG profile for `hafas-client` 2 | 3 | [*Rostocker Straßenbahn AG (RSAG)*](https://de.wikipedia.org/wiki/Rostocker_Straßenbahn_AG) is the local transport provider in [Rostock](https://en.wikipedia.org/wiki/Rostock). This profile adds *RSAG*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as rsagProfile} from 'hafas-client/p/rsag/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with RSAG profile 14 | const client = createClient(rsagProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *RSAG*-specific products 21 | -------------------------------------------------------------------------------- /p/saarfahrplan/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: '51XfsVqgbdA6oXzHrx75jhlocRg6Xe', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'ZPS-SAAR', 9 | v: 1000070, 10 | name: 'Saarfahrplan', 11 | }, 12 | endpoint: 'https://saarfahrplan.de/bin/mgate.exe', 13 | ver: '1.40', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/saarfahrplan/index.js: -------------------------------------------------------------------------------- 1 | import {parseHook} from '../../lib/profile-hooks.js'; 2 | 3 | import {parseMovement as _parseMovement} from '../../parse/movement.js'; 4 | import baseProfile from './base.js'; 5 | import {products} from './products.js'; 6 | 7 | const fixMovement = ({parsed}, m) => { 8 | // filter out empty stopovers 9 | parsed.nextStopovers = parsed.nextStopovers.filter(st => Boolean(st.stop)); 10 | return parsed; 11 | }; 12 | 13 | const profile = { 14 | ...baseProfile, 15 | locale: 'de-DE', 16 | timezone: 'Europe/Berlin', 17 | salt: Buffer.from('HJtlubisvxiJxss', 'utf8'), 18 | addMicMac: true, 19 | 20 | products: products, 21 | 22 | parseMovement: parseHook(_parseMovement, fixMovement), 23 | 24 | refreshJourneyUseOutReconL: true, 25 | trip: true, 26 | radar: true, 27 | reachableFrom: true, 28 | }; 29 | 30 | export { 31 | profile, 32 | }; 33 | -------------------------------------------------------------------------------- /p/saarfahrplan/readme.md: -------------------------------------------------------------------------------- 1 | # Saarfahrplan/VGS profile for `hafas-client` 2 | 3 | *Saarfahrplan* is the public transport information system in [Saarland](https://en.wikipedia.org/wiki/Saarland). This profile adds *Saarfahrplan*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as saarfahrplanProfile} from 'hafas-client/p/saarfahrplan/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with Saarfahrplan profile 14 | const client = createClient(saarfahrplanProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *Saarfahrplan*-specific products (such as *Saarbahn*) 21 | -------------------------------------------------------------------------------- /p/salzburg/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'wf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VAO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://verkehrsauskunft.salzburg.gv.at/bin/mgate.exe', 12 | defaultLanguage: 'de', 13 | }; 14 | -------------------------------------------------------------------------------- /p/salzburg/readme.md: -------------------------------------------------------------------------------- 1 | # Salzburg profile for `hafas-client` 2 | 3 | The government of [Salzburg](https://en.wikipedia.org/wiki/Salzburg) operates a [public transport planner](https://verkehrsauskunft.salzburg.gv.at). This profile adds support for it to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as salzburgProfile} from 'hafas-client/p/salzburg/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with Salzburg profile 14 | const client = createClient(salzburgProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/sbahn-muenchen/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'd491MVVhz9ZZts23', 5 | }, 6 | salt: 'ggnvMVV8RTt67gh1', 7 | client: { 8 | type: 'IPH', 9 | id: 'DB-REGIO-MVV', 10 | v: '5010100', 11 | name: 'MuenchenNavigator', 12 | }, 13 | endpoint: 'https://s-bahn-muenchen.hafas.de/bin/540/mgate.exe', 14 | ext: 'DB.R15.12.a', 15 | ver: '1.34', 16 | defaultLanguage: 'en', 17 | }; 18 | -------------------------------------------------------------------------------- /p/sbahn-muenchen/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | salt: Buffer.from('ggnvMVV8RTt67gh1', 'utf8'), 9 | addMicMac: true, 10 | 11 | products, 12 | 13 | refreshJourneyUseOutReconL: true, 14 | trip: true, 15 | radar: true, 16 | refreshJourney: true, 17 | reachableFrom: true, 18 | }; 19 | 20 | export { 21 | profile, 22 | }; 23 | -------------------------------------------------------------------------------- /p/sbahn-muenchen/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'ice', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'InterCityExpress', 7 | short: 'ICE', 8 | default: true, 9 | }, 10 | { 11 | id: 'ic-ec', 12 | mode: 'train', 13 | bitmasks: [2], 14 | name: 'InterCity/EuroCity', 15 | short: 'IC/EC', 16 | default: true, 17 | }, 18 | { 19 | id: 'ir-d', 20 | mode: 'train', 21 | bitmasks: [4], 22 | name: 'Interregio/Schnellzug', 23 | short: 'IRE', 24 | default: true, 25 | }, 26 | { 27 | id: 'region', 28 | mode: 'train', 29 | bitmasks: [8], 30 | name: 'Regio- und Nahverkehr', 31 | short: 'RE/RB', 32 | default: true, 33 | }, 34 | { 35 | id: 'sbahn', 36 | mode: 'train', 37 | bitmasks: [16], 38 | name: 'S-Bahn', 39 | short: 'S', 40 | default: true, 41 | }, 42 | { 43 | id: 'bus', 44 | mode: 'bus', 45 | bitmasks: [32], 46 | name: 'Bus', 47 | short: 'Bus', 48 | default: true, 49 | }, 50 | // todo: 64 51 | { 52 | id: 'ubahn', 53 | mode: 'train', 54 | bitmasks: [128], 55 | name: 'U-Bahn', 56 | short: 'U', 57 | default: true, 58 | }, 59 | { 60 | id: 'tram', 61 | mode: 'train', 62 | bitmasks: [256], 63 | name: 'Straßenbahn', 64 | short: 'Tram', 65 | default: true, 66 | }, 67 | { 68 | id: 'on-call', 69 | mode: 'taxi', // todo: or `bus`? 70 | bitmasks: [512], 71 | name: 'Anrufsammeltaxi', 72 | short: 'Sammeltaxi', 73 | default: true, 74 | }, 75 | ]; 76 | 77 | export { 78 | products, 79 | }; 80 | -------------------------------------------------------------------------------- /p/sbahn-muenchen/readme.md: -------------------------------------------------------------------------------- 1 | # S-Bahn München profile for `hafas-client` 2 | 3 | [*S-Bahn München*](https://en.wikipedia.org/wiki/Munich_S-Bahn) runs commuter trains in [Munich](https://en.wikipedia.org/wiki/Munich). This profile adapts `hafas-client` to their HAFAS endpoint. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as sMuenchenProfile} from 'hafas-client/p/sbahn-muenchen/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with S-Bahn München profile 14 | const client = createClient(sMuenchenProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /p/sncb/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'sncb-mobi', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'SNCB', 9 | v: '4030200', 10 | name: 'sncb', 11 | }, 12 | endpoint: 'https://www.belgianrail.be/jp/sncb-nmbs-routeplanner/mgate.exe', 13 | ver: '1.21', 14 | defaultLanguage: 'fr', 15 | }; 16 | -------------------------------------------------------------------------------- /p/sncb/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as sncbProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(sncbProfile, 'hafas-client-example') 7 | 8 | const gentStPieters = '8892007' 9 | const bruxellesMidi = '8814001' 10 | const gentPaddenhoek = { 11 | type: 'location', 12 | address: 'Gent, Paddenhoek', 13 | latitude: 51.0517, longitude: 3.724878, 14 | } 15 | 16 | let data = await client.locations('gent') 17 | // let data = await client.nearby(gentPaddenhoek) 18 | // let data = await client.reachableFrom(gentPaddenhoek) 19 | 20 | // let data = await client.stop(gentStPieters, {linesOfStops: true}) 21 | 22 | // let data = await client.departures(gentStPieters) 23 | // let data = await client.arrivals(gentStPieters, {duration: 10, linesOfStops: true}) 24 | 25 | // let data = await client.journeys(gentStPieters, bruxellesMidi, { 26 | // stopovers: true, 27 | // remarks: true, 28 | // }) 29 | // { 30 | // const [journey] = data.journeys 31 | // const leg = journey.legs[0] 32 | // data = await client.trip(leg.tripId, {polyline: true}) 33 | // } 34 | // { 35 | // const [journey] = data.journeys 36 | // data = await client.refreshJourney(journey.refreshToken, {remarks: true}) 37 | // } 38 | 39 | // let data = await client.radar({ 40 | // north: 51.065, 41 | // west: 3.688, 42 | // south: 51.04, 43 | // east: 3.748 44 | // }, {results: 10}) 45 | 46 | console.log(inspect(data, {depth: null, colors: true})) 47 | -------------------------------------------------------------------------------- /p/sncb/index.js: -------------------------------------------------------------------------------- 1 | import {strictEqual as eql} from 'assert'; 2 | import {parseHook} from '../../lib/profile-hooks.js'; 3 | import {parseLine} from '../../parse/line.js'; 4 | import baseProfile from './base.js'; 5 | import {products} from './products.js'; 6 | 7 | // todo: this is ugly 8 | const lineNameWithoutFahrtNr = ({parsed}) => { 9 | const {name, fahrtNr} = parsed; 10 | if (!name || !fahrtNr || !(/s\d/i).test(name)) { 11 | return parsed; 12 | } 13 | const i = name.indexOf(fahrtNr); 14 | if (i < 0) { 15 | return parsed; 16 | } 17 | 18 | if ( 19 | (/\s/).test(name[i - 1] || '') // space before 20 | && name.length === i + fahrtNr.length // nothing behind 21 | ) { 22 | return { 23 | ...parsed, 24 | name: name.slice(0, i - 1) + name.slice(i + fahrtNr.length + 1), 25 | }; 26 | } 27 | return parsed; 28 | }; 29 | eql(lineNameWithoutFahrtNr({ 30 | parsed: {name: 'THA 123', fahrtNr: '123'}, 31 | }).name, 'THA 123'); 32 | eql(lineNameWithoutFahrtNr({ 33 | parsed: {name: 'S1 123', fahrtNr: '123'}, 34 | }).name, 'S1'); 35 | eql(lineNameWithoutFahrtNr({ 36 | parsed: {name: 'S1-123', fahrtNr: '123'}, 37 | }).name, 'S1-123'); 38 | eql(lineNameWithoutFahrtNr({ 39 | parsed: {name: 'S1 123a', fahrtNr: '123'}, 40 | }).name, 'S1 123a'); 41 | 42 | const profile = { 43 | ...baseProfile, 44 | locale: 'fr-BE', 45 | timezone: 'Europe/Brussels', 46 | 47 | products, 48 | 49 | parseLine: parseHook(parseLine, lineNameWithoutFahrtNr), 50 | 51 | trip: true, 52 | refreshJourney: true, 53 | radar: true, 54 | reachableFrom: true, 55 | }; 56 | 57 | export { 58 | profile, 59 | }; 60 | -------------------------------------------------------------------------------- /p/sncb/products.js: -------------------------------------------------------------------------------- 1 | // https://www.belgiantrain.be/en/support/faq/faq-routes-schedules/faq-train-types 2 | const products = [ // todo: 2, 8, 32, 128 3 | { 4 | id: 'high-speed-train', 5 | mode: 'train', 6 | bitmasks: [1], 7 | name: 'high-speed train', 8 | short: 'HST', 9 | default: true, 10 | }, 11 | { 12 | id: 'intercity-p', 13 | mode: 'train', 14 | bitmasks: [4], 15 | name: 'InterCity/Peak', 16 | short: 'IC/P', 17 | default: true, 18 | }, 19 | { 20 | id: 's-train', 21 | mode: 'train', 22 | bitmasks: [16], 23 | name: 'S-train', 24 | short: 'S', 25 | default: true, 26 | }, 27 | { 28 | id: 'local-train', 29 | mode: 'train', 30 | bitmasks: [64], 31 | name: 'local train', 32 | short: 'L', 33 | default: true, 34 | }, 35 | { 36 | id: 'metro', 37 | mode: 'train', 38 | bitmasks: [256], 39 | name: 'Metro', 40 | short: 'M', 41 | default: true, 42 | }, 43 | { 44 | id: 'bus', 45 | mode: 'bus', 46 | bitmasks: [512], 47 | name: 'bus', 48 | short: 'bus', 49 | default: true, 50 | }, 51 | { 52 | id: 'tram', 53 | mode: 'train', 54 | bitmasks: [1024], 55 | name: 'tram', 56 | short: 'tram', 57 | default: true, 58 | }, 59 | ]; 60 | 61 | export { 62 | products, 63 | }; 64 | -------------------------------------------------------------------------------- /p/sncb/readme.md: -------------------------------------------------------------------------------- 1 | # SNCB profile for `hafas-client` 2 | 3 | *Note:* **This profile is currently broken** because [SNCB has switched the HAFAS API style](https://github.com/public-transport/hafas-client/issues/284) and we haven't migrated to the new API. 4 | 5 | [*Société nationale des chemins de fer belges (SNCB)*/*Nationale Maatschappij der Belgische Spoorwegen (NMBS)*](https://en.wikipedia.org/wiki/National_Railway_Company_of_Belgium) is the major public transport provider of [Belgium](https://en.wikipedia.org/wiki/Belgium). This profile adds *SNCB*-specific customizations to `hafas-client`. 6 | 7 | ## Usage 8 | 9 | ```js 10 | import {createClient} from 'hafas-client' 11 | import {profile as sncbProfile} from 'hafas-client/p/sncb/index.js' 12 | 13 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 14 | 15 | // create a client with SNCB profile 16 | const client = createClient(sncbProfile, userAgent) 17 | ``` 18 | 19 | 20 | ## Customisations 21 | 22 | - parses *SNCB*-specific products 23 | -------------------------------------------------------------------------------- /p/stv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'wf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VAO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://verkehrsauskunft.verbundlinie.at/bin/mgate.exe', 12 | ext: 'VAO.13', 13 | ver: '1.32', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/stv/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as stvProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(stvProfile, 'hafas-client example') 7 | 8 | const grazSonnenhang = '460413500' 9 | const grazHödlweg = '460415400' 10 | const grazEisengasse10 = { 11 | type: 'location', 12 | id: '980027564', 13 | address: 'Eisengasse 10, 8020 Graz', 14 | latitude: 47.076553, 15 | longitude: 15.406064, 16 | } 17 | 18 | let data = await client.locations('sonnenhang', {results: 3}) 19 | // let data = await client.nearby(grazEisengasse10, {distance: 1000}) 20 | // let data = await client.reachableFrom(grazEisengasse10, { 21 | // maxDuration: 30, 22 | // }) 23 | 24 | // let data = await client.stop(grazSonnenhang, {linesOfStops: true}) 25 | 26 | // let data = await client.departures(grazSonnenhang, {duration: 1}) 27 | // let data = await client.arrivals(grazSonnenhang, {duration: 10, linesOfStops: true}) 28 | 29 | // let data = await client.journeys(grazSonnenhang, grazHödlweg, { 30 | // results: 1, stopovers: true, 31 | // }) 32 | // { 33 | // const [journey] = data.journeys 34 | // data = await client.refreshJourney(journey.refreshToken, { 35 | // stopovers: true, 36 | // remarks: true, 37 | // }) 38 | // } 39 | // { 40 | // const [journey] = data.journeys 41 | // const leg = journey.legs[0] 42 | // data = await client.trip(leg.tripId, {polyline: true}) 43 | // } 44 | 45 | console.log(inspect(data, {depth: null, colors: true})) 46 | -------------------------------------------------------------------------------- /p/stv/readme.md: -------------------------------------------------------------------------------- 1 | # STV profile for `hafas-client` 2 | 3 | [*Steirischer Verkehrsverbund (STV)*](https://de.wikipedia.org/wiki/Steirischer_Verkehrsverbund) is the local transport provider of [Styria](https://en.wikipedia.org/wiki/Styria) that runs its apps under the [*VerbundLinie*](https://www.verbundlinie.at) brand. This profile adds *STV*/*VerbundLinie* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as stvProfile} from 'hafas-client/p/stv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with STV profile 14 | const client = createClient(stvProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/svv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'wf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VAO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://fahrplan.salzburg-verkehr.at/bin/mgate.exe', 12 | ext: 'VAO.11', 13 | ver: '1.39', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/svv/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as svvProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(svvProfile, 'hafas-client-example') 7 | 8 | const sam = '455086100' 9 | const volksgarten = '455082100' 10 | const zillnerstr2 = { 11 | type: 'location', 12 | id: '980133005', 13 | address: 'Zillnerstraße 2, 5020 Salzburg', 14 | latitude: 47.801434, longitude: 13.031006, 15 | } 16 | 17 | let data = await client.locations('salzburg sam', {results: 2}) 18 | // let data = await client.nearby(zillnerstr2) 19 | // let data = await client.reachableFrom(zillnerstr2, { 20 | // when: new Date('2020-06-01T10:00:00+0200'), 21 | // }) 22 | 23 | // let data = await client.stop(sam, {linesOfStops: true}) 24 | 25 | // let data = await client.departures(sam, {duration: 1}) 26 | // let data = await client.arrivals(sam, {duration: 10, linesOfStops: true}) 27 | 28 | // let data = await client.journeys(sam, volksgarten, {results: 1, polylines: true}) 29 | // { 30 | // const [journey] = data.journeys 31 | // data = await client.refreshJourney(journey.refreshToken, { 32 | // stopovers: true, 33 | // remarks: true, 34 | // }) 35 | // } 36 | // { 37 | // const [journey] = data.journeys 38 | // const leg = journey.legs[0] 39 | // data = await client.trip(leg.tripId, {polyline: true}) 40 | // } 41 | 42 | console.log(inspect(data, {depth: null, colors: true})) 43 | -------------------------------------------------------------------------------- /p/svv/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'at-DE', 7 | timezone: 'Europe/Vienna', 8 | 9 | products, 10 | 11 | trip: true, 12 | refreshJourney: true, 13 | reachableFrom: true, 14 | refreshJourneyUseOutReconL: true, 15 | }; 16 | 17 | export { 18 | profile, 19 | }; 20 | -------------------------------------------------------------------------------- /p/svv/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'bahn-s-bahn', 4 | mode: 'train', 5 | bitmasks: [1, 2], 6 | name: 'Bahn & S-Bahn', 7 | short: 'S/Zug', 8 | default: true, 9 | }, 10 | { 11 | id: 'u-bahn', 12 | mode: 'train', 13 | bitmasks: [4], 14 | name: 'U-Bahn', 15 | short: 'U', 16 | default: true, 17 | }, 18 | { 19 | id: 'strassenbahn', 20 | mode: 'train', 21 | bitmasks: [16], 22 | name: 'Strassenbahn', 23 | short: 'Str', 24 | default: true, 25 | }, 26 | { 27 | id: 'fernbus', 28 | mode: 'bus', 29 | bitmasks: [32], 30 | name: 'Fernbus', 31 | short: 'Bus', 32 | default: true, 33 | }, 34 | { 35 | id: 'regionalbus', 36 | mode: 'bus', 37 | bitmasks: [64], 38 | name: 'Regionalbus', 39 | short: 'Bus', 40 | default: true, 41 | }, 42 | { 43 | id: 'stadtbus', 44 | mode: 'bus', 45 | bitmasks: [128], 46 | name: 'Stadtbus', 47 | short: 'Bus', 48 | default: true, 49 | }, 50 | { 51 | id: 'seilbahn-zahnradbahn', 52 | mode: 'gondola', 53 | bitmasks: [256], 54 | name: 'Seil-/Zahnradbahn', 55 | short: 'Seil-/Zahnradbahn', 56 | default: true, 57 | }, 58 | { 59 | id: 'schiff', 60 | mode: 'watercraft', 61 | bitmasks: [512], 62 | name: 'Schiff', 63 | short: 'F', 64 | default: true, 65 | }, 66 | ]; 67 | 68 | export { 69 | products, 70 | }; 71 | -------------------------------------------------------------------------------- /p/svv/readme.md: -------------------------------------------------------------------------------- 1 | # SVV profile for `hafas-client` 2 | 3 | [*Salzburger Verkehrsverbund (SVV)*](https://de.wikipedia.org/wiki/Salzburger_Verkehrsverbund) is the local transit authority of [Salzburg](https://en.wikipedia.org/wiki/Salzburg). This profile adds *SVV*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as svvProfile} from 'hafas-client/p/svv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with SVV profile 14 | const client = createClient(svvProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *SVV*-specific products 21 | -------------------------------------------------------------------------------- /p/tpg/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: '9CZsdl5PqX8n5D6b', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'HAFAS', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://tpg-webapp.hafas.de/bin/mgate.exe', 12 | defaultLanguage: 'fr', 13 | }; 14 | -------------------------------------------------------------------------------- /p/tpg/readme.md: -------------------------------------------------------------------------------- 1 | # TPG profile for `hafas-client` 2 | 3 | [*Transports publics genevois (TPG)*](https://en.wikipedia.org/wiki/Geneva_Public_Transport) is the local transport provider of the [Canton of Geneva](https://en.wikipedia.org/wiki/Canton_of_Geneva). This profile adds *TPG* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as tpgProfile} from 'hafas-client/p/tpg/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with TPG profile 14 | const client = createClient(tpgProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/vbb/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'hafas-vbb-webapp', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VBB', 9 | name: 'VBB WebApp', 10 | l: 'vs_webapp_vbb', 11 | }, 12 | endpoint: 'https://fahrinfo.vbb.de/bin/mgate.exe', 13 | ver: '1.45', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/vbb/parse-loc-dhid.js: -------------------------------------------------------------------------------- 1 | const dhidPrefix = 'A×'; 2 | 3 | const parseAndAddLocationDHID = (loc, l) => { 4 | if (!Array.isArray(l.gidL)) { 5 | return; 6 | } 7 | 8 | const dhidGid = l.gidL.find(gid => gid.slice(0, dhidPrefix.length) === dhidPrefix); 9 | if (!dhidGid) { 10 | return; 11 | } 12 | const dhid = dhidGid.slice(dhidPrefix.length); 13 | 14 | // It seems that the DHID of the parent station is being used, not of the stop. 15 | // if (!loc.ids) loc.ids = {} 16 | // loc.ids.dhid = dhid 17 | // todo: use loc.ids.stationDHID instead? 18 | loc.stationDHID = dhid; 19 | }; 20 | 21 | export { 22 | parseAndAddLocationDHID, 23 | }; 24 | -------------------------------------------------------------------------------- /p/vbb/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'suburban', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'S-Bahn', 7 | short: 'S', 8 | default: true, 9 | }, 10 | { 11 | id: 'subway', 12 | mode: 'train', 13 | bitmasks: [2], 14 | name: 'U-Bahn', 15 | short: 'U', 16 | default: true, 17 | }, 18 | { 19 | id: 'tram', 20 | mode: 'train', 21 | bitmasks: [4], 22 | name: 'Tram', 23 | short: 'T', 24 | default: true, 25 | }, 26 | { 27 | id: 'bus', 28 | mode: 'bus', 29 | bitmasks: [8], 30 | name: 'Bus', 31 | short: 'B', 32 | default: true, 33 | }, 34 | { 35 | id: 'ferry', 36 | mode: 'watercraft', 37 | bitmasks: [16], 38 | name: 'Fähre', 39 | short: 'F', 40 | default: true, 41 | }, 42 | { 43 | id: 'express', 44 | mode: 'train', 45 | bitmasks: [32], 46 | name: 'IC/ICE', 47 | short: 'E', 48 | default: true, 49 | }, 50 | { 51 | id: 'regional', 52 | mode: 'train', 53 | bitmasks: [64], 54 | name: 'RB/RE', 55 | short: 'R', 56 | default: true, 57 | }, 58 | ]; 59 | 60 | export { 61 | products, 62 | }; 63 | -------------------------------------------------------------------------------- /p/vbb/readme.md: -------------------------------------------------------------------------------- 1 | # VBB profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Berlin-Brandenburg (VBB)*](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) is a group of public transport companies, running the public transport network in [Berlin](https://en.wikipedia.org/wiki/Berlin). This profile adds *VBB*-specific customizations to `hafas-client`. Consider using [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas#vbb-hafas), to always get the customized client right away. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VBB profile 14 | const client = createClient(vbbProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *VBB*-specific products (such as *X-Bus*) 21 | - renames *Ringbahn* line names to contain `⟳` and `⟲` 22 | -------------------------------------------------------------------------------- /p/vbn/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'kaoxIXLn03zCr2KR', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'VBN', 9 | v: '6000000', 10 | name: 'vbn', 11 | }, 12 | endpoint: 'https://fahrplaner.vbn.de/bin/mgate.exe', 13 | ver: '1.42', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/vbn/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | // https://runkit.com/derhuerst/hafas-decrypt-encrypted-mac-salt 9 | // https://gist.github.com/derhuerst/fd2f81a597bde66cb1f689006d574d7f#file-config-txt-L22-L23 10 | salt: Buffer.from('SP31mBufSyCLmNxp', 'utf-8'), 11 | addMicMac: true, 12 | 13 | products: products, 14 | 15 | trip: true, 16 | radar: true, 17 | reachableFrom: true, 18 | refreshJourneyUseOutReconL: true, 19 | }; 20 | 21 | export { 22 | profile, 23 | }; 24 | -------------------------------------------------------------------------------- /p/vbn/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'express-train', 4 | mode: 'train', 5 | bitmasks: [1], 6 | name: 'InterCityExpress', 7 | short: 'ICE', 8 | default: true, 9 | }, 10 | { 11 | id: 'national-train', 12 | mode: 'train', 13 | bitmasks: [2, 4], 14 | name: 'InterCity, EuroCity, CityNightLine, InterRegio', 15 | short: 'IC/EC/CNL/IR', 16 | default: true, 17 | }, 18 | { 19 | id: 'local-train', 20 | mode: 'train', 21 | bitmasks: [8], 22 | name: 'Nahverkehr', 23 | short: 'Nahv.', 24 | default: true, 25 | }, 26 | { 27 | id: 'suburban', 28 | mode: 'train', 29 | bitmasks: [16], 30 | name: 'S-Bahn', 31 | short: 'S', 32 | default: true, 33 | }, 34 | { 35 | id: 'bus', 36 | mode: 'bus', 37 | bitmasks: [32], 38 | name: 'Bus', 39 | short: 'Bus', 40 | default: true, 41 | }, 42 | { 43 | id: 'watercraft', 44 | mode: 'watercraft', 45 | bitmasks: [64], 46 | name: 'Schiff', 47 | short: 'Schiff', 48 | default: true, 49 | }, 50 | { 51 | id: 'subway', 52 | mode: 'train', 53 | bitmasks: [128], 54 | name: 'U-Bahn', 55 | short: 'U', 56 | default: true, 57 | }, 58 | { 59 | id: 'tram', 60 | mode: 'train', 61 | bitmasks: [256], 62 | name: 'Tram', 63 | short: 'Tram', 64 | default: true, 65 | }, 66 | { 67 | id: 'dial-a-ride', 68 | mode: 'taxi', // todo: or `bus`? 69 | bitmasks: [256], 70 | name: 'Anrufverkehr', 71 | short: 'AST', 72 | default: true, 73 | }, 74 | ]; 75 | 76 | export { 77 | products, 78 | }; 79 | -------------------------------------------------------------------------------- /p/vbn/readme.md: -------------------------------------------------------------------------------- 1 | # VBN profile for `hafas-client` 2 | 3 | The [*Verkehrsverbund Bremen/Niedersachsen (VBN)*](https://de.wikipedia.org/wiki/Verkehrsverbund_Bremen/Niedersachsen) is a public transportation provider for [Lower Saxony](https://en.wikipedia.org/wiki/Lower_Saxony). This profile adds *VBN*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vbnProfile} from 'hafas-client/p/vbn/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VBN profile 14 | const client = createClient(vbnProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *VBN*-specific products (such as *Anrufverkehr*) 21 | -------------------------------------------------------------------------------- /p/vkg/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'wf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VAO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://routenplaner.kaerntner-linien.at/bin/mgate.exe', 12 | defaultLanguage: 'de', 13 | }; 14 | -------------------------------------------------------------------------------- /p/vkg/readme.md: -------------------------------------------------------------------------------- 1 | # VKG profile for `hafas-client` 2 | 3 | [*Kärntner Linien/Verkehrsverbund Kärnten (VKG/VVK)*](https://de.wikipedia.org/wiki/Verkehrsverbund_Kärnten) is the local transport provider of [Carinthia](https://en.wikipedia.org/wiki/Carinthia). This profile adds *VKG* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vkgProfile} from 'hafas-client/p/vkg/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VKG profile 14 | const client = createClient(vkgProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /p/vmt/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 't2h7u1e6r4i8n3g7e0n', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'HAFAS', 9 | v: '2040100', 10 | name: 'VMT', 11 | }, 12 | endpoint: 'https://vmt.hafas.de/bin/ticketing/mgate.exe', 13 | ver: '1.34', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/vmt/example.js: -------------------------------------------------------------------------------- 1 | import {inspect} from 'util' 2 | import {createClient} from '../../index.js' 3 | import {profile as vmtProfile} from './index.js' 4 | 5 | // Pick a descriptive user agent! hafas-client won't work with this string. 6 | const client = createClient(vmtProfile, 'hafas-client-example') 7 | 8 | const jena = '190014' 9 | const gothaZOB = '167280' 10 | 11 | let data = await client.locations('ohrdruf', {results: 2}) 12 | // let data = await client.nearby({ 13 | // type: 'location', 14 | // latitude: 50.975615, 15 | // longitude: 11.032374 16 | // }) 17 | // let data = await client.reachableFrom({ 18 | // type: 'location', 19 | // id: '980348376', 20 | // address: 'Erfurt, Grafengasse 12', 21 | // latitude: 50.975993, longitude: 11.031553 22 | // }, { 23 | // when: new Date('2020-03-04T10:00:00+01:00') 24 | // }) 25 | 26 | // let data = await client.stop(jena, {linesOfStops: true}) // Dammtor 27 | 28 | // let data = await client.departures(jena) 29 | // let data = await client.arrivals(jena, {duration: 10, linesOfStops: true}) 30 | 31 | // let data = await client.journeys(jena, gothaZOB, {results: 1}) 32 | // { 33 | // const [journey] = data.journeys 34 | // data = await client.refreshJourney(journey.refreshToken, { 35 | // stopovers: true, 36 | // remarks: true, 37 | // }) 38 | // } 39 | // { 40 | // const [journey] = data.journeys 41 | // const leg = journey.legs[0] 42 | // data = await client.trip(leg.tripId, {polyline: true}) 43 | // } 44 | 45 | console.log(inspect(data, {depth: null, colors: true})) 46 | -------------------------------------------------------------------------------- /p/vmt/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | // https://runkit.com/derhuerst/hafas-decrypt-encrypted-mac-salt 9 | // https://gist.github.com/derhuerst/b20adde9f614ceb6b2a8b9c7f4487da8#file-hafas-config-L31-L32 10 | salt: Buffer.from('7x8d3n2a5m1b3c6z', 'utf-8'), 11 | addMicMac: true, 12 | 13 | products, 14 | 15 | refreshJourneyUseOutReconL: true, 16 | trip: true, 17 | reachableFrom: true, 18 | remarks: false, // seems like ver >= 1.20 is required 19 | }; 20 | 21 | export { 22 | profile, 23 | }; 24 | -------------------------------------------------------------------------------- /p/vmt/products.js: -------------------------------------------------------------------------------- 1 | // todo: what is `512`? 2 | const products = [ 3 | { 4 | id: 'long-distance-train', 5 | mode: 'train', 6 | bitmasks: [1, 2, 4], 7 | name: 'long-distance train', 8 | short: 'ICE/IC/EC', 9 | default: true, 10 | }, 11 | { 12 | id: 'regional-train', 13 | mode: 'train', 14 | // 8 is *not* always RB, 16 is *not* always RE! 15 | bitmasks: [8, 16], 16 | name: 'regional train', 17 | short: 'RE/RB', 18 | default: true, 19 | }, 20 | { 21 | id: 'tram', 22 | mode: 'train', 23 | bitmasks: [32], 24 | name: 'tram', 25 | short: 'tram', 26 | default: true, 27 | }, 28 | // todo: what are `64` & `128`? 29 | { 30 | id: 'bus', 31 | mode: 'bus', 32 | bitmasks: [256], 33 | name: 'bus', 34 | short: 'bus', 35 | default: true, 36 | }, 37 | ]; 38 | 39 | export { 40 | products, 41 | }; 42 | -------------------------------------------------------------------------------- /p/vmt/readme.md: -------------------------------------------------------------------------------- 1 | # VMT profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Mittelthüringen (VMT)*](https://en.wikipedia.org/wiki/Verkehrsverbund_Mittelthüringen) is a major local transport authority in [Thuringia](https://en.wikipedia.org/wiki/Thuringia). This profile adds *VMT*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vmtProfile} from 'hafas-client/p/vmt/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VMT profile 14 | const client = createClient(vmtProfile, userAgent) 15 | ``` 16 | 17 | ## Customisations 18 | 19 | - parses *VMT*-specific products 20 | -------------------------------------------------------------------------------- /p/vor/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'and20201hf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'VAO', 9 | }, 10 | endpoint: 'https://anachb.vor.at/bin/mgate.exe', 11 | ext: 'VAO.11', 12 | ver: '1.27', 13 | defaultLanguage: 'de', 14 | }; 15 | -------------------------------------------------------------------------------- /p/vor/readme.md: -------------------------------------------------------------------------------- 1 | # VOR profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Ost-Region (VOR)*](https://de.wikipedia.org/wiki/Verkehrsverbund_Ost-Region) is the local transport provider of [Vienna](https://en.wikipedia.org/wiki/Vienna), [Lower Austria](https://en.wikipedia.org/wiki/Lower_Austria) and [Burgenland](https://en.wikipedia.org/wiki/Burgenland); It runs its apps under the [*AnachB*](https://anachb.vor.at) brand. This profile adds *VOR*/*AnachB* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vorProfile} from 'hafas-client/p/vor/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VOR profile 14 | const client = createClient(vorProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/vos/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'PnYowCQP7Tp1V', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'SWO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://fahrplan.vos.info/bin/mgate.exe', 12 | defaultLanguage: 'de', 13 | }; 14 | -------------------------------------------------------------------------------- /p/vos/readme.md: -------------------------------------------------------------------------------- 1 | # VOS profile for `hafas-client` 2 | 3 | [*Verkehrsgemeinschaft Osnabrück (VOS)*](https://de.wikipedia.org/wiki/Verkehrsgemeinschaft_Osnabrück) is the local transport provider of [Osnabrück](https://en.wikipedia.org/wiki/Osnabrück). This profile adds *VOS* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vosProfile} from 'hafas-client/p/vos/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VOS profile 14 | const client = createClient(vosProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /p/vrn/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'p091VRNZz79KtUz5', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'DB-REGIO-VRN', 9 | v: '6000400', 10 | name: 'VRN', 11 | }, 12 | endpoint: 'https://vrn.hafas.de/bin/mgate.exe', 13 | ext: 'DB.R19.04.a', 14 | ver: '1.34', 15 | defaultLanguage: 'de', 16 | }; 17 | -------------------------------------------------------------------------------- /p/vrn/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | 9 | products, 10 | 11 | trip: true, 12 | radar: true, 13 | reachableFrom: true, 14 | refreshJourney: true, 15 | refreshJourneyUseOutReconL: true, 16 | }; 17 | 18 | export { 19 | profile, 20 | }; 21 | -------------------------------------------------------------------------------- /p/vrn/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | // todo: what is `64`? 3 | { 4 | id: 'regional-train', 5 | mode: 'train', 6 | bitmasks: [8], 7 | name: 'regional train', 8 | short: 'RE/RB', 9 | default: true, 10 | }, 11 | { 12 | id: 'urban-train', 13 | mode: 'train', 14 | bitmasks: [16], 15 | name: 'urban train', 16 | short: 'S', 17 | default: true, 18 | }, 19 | { 20 | id: 'subway', 21 | mode: 'train', 22 | bitmasks: [128], 23 | name: 'subway', 24 | short: 'U', 25 | default: true, 26 | }, 27 | { 28 | id: 'tram', 29 | mode: 'train', 30 | bitmasks: [256], 31 | name: 'tram', 32 | short: 'Tram', 33 | default: true, 34 | }, 35 | { 36 | id: 'bus', 37 | mode: 'bus', 38 | bitmasks: [32], 39 | name: 'Bus', 40 | short: 'Bus', 41 | default: true, 42 | }, 43 | { 44 | id: 'dial-a-ride', 45 | mode: 'taxi', 46 | bitmasks: [512], 47 | name: 'dial-a-ride', 48 | short: 'taxi', 49 | default: true, 50 | }, 51 | { 52 | id: 'long-distance-train', 53 | mode: 'train', 54 | bitmasks: [1, 2, 4], 55 | name: 'long-distance train', 56 | short: 'ICE/IC/EC/EN', 57 | default: false, 58 | }, 59 | ]; 60 | 61 | export { 62 | products, 63 | }; 64 | -------------------------------------------------------------------------------- /p/vrn/readme.md: -------------------------------------------------------------------------------- 1 | # VRN profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Rhein-Neckar (VRN)*](https://en.wikipedia.org/wiki/Verkehrsverbund_Rhein-Neckar) is a public transport network in south-west Germany. This profile adds *VRN*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vrnProfile} from 'hafas-client/p/vrn/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VRN profile 14 | const client = createClient(vrnProfile, userAgent) 15 | ``` 16 | 17 | 18 | ## Customisations 19 | 20 | - parses *VRN*-specific products 21 | -------------------------------------------------------------------------------- /p/vsn/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'Mpf5UPC0DmzV8jkg', 5 | }, 6 | client: { 7 | type: 'IPA', 8 | id: 'VSN', 9 | v: '5030100', 10 | name: 'vsn', 11 | }, 12 | endpoint: 'https://fahrplaner.vsninfo.de/hafas/mgate.exe', 13 | ver: '1.42', 14 | defaultLanguage: 'de', 15 | }; 16 | -------------------------------------------------------------------------------- /p/vsn/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-DE', 7 | timezone: 'Europe/Berlin', 8 | // https://gist.github.com/n0emis/3b6887572793f4f54da9d83b30548332#file-haf_config_base-properties-L31 9 | // https://runkit.com/derhuerst/hafas-decrypt-encrypted-mac-salt 10 | salt: Buffer.from('SP31mBufSyCLmNxp', 'utf8'), 11 | addMicMac: true, 12 | 13 | products: products, 14 | 15 | refreshJourneyUseOutReconL: true, 16 | trip: true, 17 | radar: true, 18 | reachableFrom: true, 19 | }; 20 | 21 | export { 22 | profile, 23 | }; 24 | -------------------------------------------------------------------------------- /p/vsn/readme.md: -------------------------------------------------------------------------------- 1 | # VSN profile for `hafas-client` 2 | [*Verkehrsverbund Süd-Niedersachsen (VSN)*](https://de.wikipedia.org/wiki/Verkehrsverbund_S%C3%BCd-Niedersachsen) is the local transport provider south [Lower Saxony](https://en.wikipedia.org/wiki/Lower_Saxony). This profile adds *VSN*-specific customizations to `hafas-client`. 3 | 4 | ## Usage 5 | ```js 6 | import {createClient} from 'hafas-client' 7 | import {profile as vsnProfile} from 'hafas-client/p/vsn/index.js' 8 | 9 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 10 | 11 | // create a client with VSN profile 12 | const client = createClient(vsnProfile, userAgent) 13 | ``` 14 | 15 | ## Customisations 16 | - parses *VSN*-specific products 17 | -------------------------------------------------------------------------------- /p/vvt/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'wf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'WEB', 8 | id: 'VAO', 9 | name: 'webapp', 10 | }, 11 | endpoint: 'https://smartride.vvt.at/bin/mgate.exe', 12 | defaultLanguage: 'en', 13 | }; 14 | -------------------------------------------------------------------------------- /p/vvt/readme.md: -------------------------------------------------------------------------------- 1 | # VVT profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Tirol (VVT)*](https://de.wikipedia.org/wiki/Verkehrsverbund_Tirol) is the regional transport provider of [Tyrol](https://en.wikipedia.org/wiki/Tyrol). This profile adds *VVT* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vvtProfile} from 'hafas-client/p/vvt/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VVT profile 14 | const client = createClient(vvtProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /p/vvv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'and20201hf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'AND', 8 | id: 'VAO', 9 | }, 10 | endpoint: 'https://fahrplan.vmobil.at/bin/mgate.exe', 11 | ext: 'VAO.11', 12 | ver: '1.27', 13 | defaultLanguage: 'de', 14 | }; 15 | -------------------------------------------------------------------------------- /p/vvv/readme.md: -------------------------------------------------------------------------------- 1 | # VVV profile for `hafas-client` 2 | 3 | [*Verkehrsverbund Vorarlberg (VVV)*](https://de.wikipedia.org/wiki/Verkehrsverbund_Vorarlberg) is the local transport provider of [Vorarlberg](https://en.wikipedia.org/wiki/Vorarlberg) that runs its apps under the [*VMOBIL*](https://www.vmobil.at) brand. This profile adds *VVV*/*VMOBIL* support to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as vvvProfile} from 'hafas-client/p/vvv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with VVV profile 14 | const client = createClient(vvvProfile, userAgent) 15 | ``` 16 | 17 | Check out the [code examples](example.js). 18 | -------------------------------------------------------------------------------- /p/zvv/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | auth: { 3 | type: 'AID', 4 | aid: 'hf7mcf9bv3nv8g5f', 5 | }, 6 | client: { 7 | type: 'IPH', 8 | id: 'ZVV', 9 | v: '6000400', 10 | name: 'zvvPROD-STORE', 11 | }, 12 | endpoint: 'https://online.fahrplan.zvv.ch/gate', 13 | ext: 'ZVV.2', 14 | ver: '1.42', 15 | defaultLanguage: 'de', 16 | }; 17 | -------------------------------------------------------------------------------- /p/zvv/index.js: -------------------------------------------------------------------------------- 1 | import baseProfile from './base.js'; 2 | import {products} from './products.js'; 3 | 4 | const profile = { 5 | ...baseProfile, 6 | locale: 'de-CH', 7 | timezone: 'Europe/Zurich', 8 | 9 | products, 10 | 11 | trip: true, 12 | radar: true, 13 | refreshJourneyUseOutReconL: true, 14 | reachableFrom: true, 15 | }; 16 | 17 | export { 18 | profile, 19 | }; 20 | -------------------------------------------------------------------------------- /p/zvv/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | id: 'high-speed-train', 4 | mode: 'train', 5 | bitmasks: [1, 2, 4, 8], 6 | name: 'High Speed Train', 7 | short: 'High Speed', 8 | default: true, 9 | }, 10 | { 11 | id: 'urban-train', 12 | mode: 'train', 13 | bitmasks: [32], 14 | name: 'Urban Train', 15 | short: 'Urban', 16 | default: true, 17 | }, 18 | { 19 | id: 'tram', 20 | mode: 'train', 21 | bitmasks: [512], 22 | name: 'Tram', 23 | short: 'Tram', 24 | default: true, 25 | }, 26 | { 27 | id: 'bus', 28 | mode: 'bus', 29 | bitmasks: [64], 30 | name: 'Bus', 31 | short: 'Bus', 32 | default: true, 33 | }, 34 | { 35 | id: 'boat', 36 | mode: 'watercraft', 37 | bitmasks: [16], 38 | name: 'Boat', 39 | short: 'Boat', 40 | default: true, 41 | }, 42 | { 43 | id: 'cable-car', 44 | mode: 'gondola', 45 | bitmasks: [128], 46 | name: 'Cable Car', 47 | short: 'Cable Car', 48 | default: true, 49 | }, 50 | { 51 | id: 'night-train', 52 | mode: 'train', 53 | bitmasks: [256], 54 | name: 'Night Train', 55 | short: 'Night Train', 56 | default: true, 57 | }, 58 | ]; 59 | 60 | export { 61 | products, 62 | }; 63 | -------------------------------------------------------------------------------- /p/zvv/readme.md: -------------------------------------------------------------------------------- 1 | # ZVV profile for `hafas-client` 2 | 3 | [*Zürcher Verkehrsverbund (ZVV)*](https://en.wikipedia.org/wiki/Zürcher_Verkehrsverbund) is the local transport system of [Zürich](https://en.wikipedia.org/wiki/Zürich). This profile adds *ZVV*-specific customizations to `hafas-client`. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import {createClient} from 'hafas-client' 9 | import {profile as zvvProfile} from 'hafas-client/p/zvv/index.js' 10 | 11 | const userAgent = 'link-to-your-project-or-email' // adapt this to your project! 12 | 13 | // create a client with ZVV profile 14 | const client = createClient(zvvProfile, userAgent) 15 | ``` 16 | -------------------------------------------------------------------------------- /parse/arrival.js: -------------------------------------------------------------------------------- 1 | import {createParseArrOrDep} from './arrival-or-departure.js'; 2 | 3 | const ARRIVAL = 'a'; 4 | const parseArrival = createParseArrOrDep(ARRIVAL); 5 | 6 | export { 7 | parseArrival, 8 | }; 9 | -------------------------------------------------------------------------------- /parse/date-time.js: -------------------------------------------------------------------------------- 1 | import {DateTime, FixedOffsetZone, IANAZone} from 'luxon'; 2 | import {luxonIANAZonesByProfile as timezones} from '../lib/luxon-timezones.js'; 3 | 4 | const parseDaysOffset = (_, time) => { 5 | return time.length > 6 6 | ? parseInt(time.slice(0, -6)) 7 | : 0; 8 | }; 9 | 10 | const parseDateTime = (ctx, date, time, tzOffset = null, timestamp = false) => { 11 | const {profile} = ctx; 12 | 13 | const pDate = [date.substr(-8, 4), date.substr(-4, 2), date.substr(-2, 2)]; 14 | if (!pDate[0] || !pDate[1] || !pDate[2]) { 15 | throw new Error('invalid date format: ' + date); 16 | } 17 | 18 | const pTime = [time.substr(-6, 2), time.substr(-4, 2), time.substr(-2, 2)]; 19 | if (!pTime[0] || !pTime[1] || !pTime[2]) { 20 | throw new Error('invalid time format: ' + time); 21 | } 22 | 23 | const daysOffset = parseDaysOffset(ctx, time); 24 | 25 | let timezone; 26 | if (tzOffset !== null) { 27 | timezone = FixedOffsetZone.instance(tzOffset); 28 | } else if (timezones.has(profile)) { 29 | timezone = timezones.get(profile); 30 | } else { 31 | timezone = new IANAZone(profile.timezone); 32 | timezones.set(profile, timezone); 33 | } 34 | 35 | let dt = DateTime.fromISO(pDate.join('-') + 'T' + pTime.join(':'), { 36 | locale: profile.locale, 37 | zone: timezone, 38 | }); 39 | if (daysOffset > 0) { 40 | dt = dt.plus({days: daysOffset}); 41 | } 42 | return timestamp 43 | ? dt.toMillis() 44 | : dt.toISO({suppressMilliseconds: true}); 45 | }; 46 | 47 | export { 48 | parseDateTime, 49 | }; 50 | -------------------------------------------------------------------------------- /parse/departure.js: -------------------------------------------------------------------------------- 1 | import {createParseArrOrDep} from './arrival-or-departure.js'; 2 | 3 | const DEPARTURE = 'd'; 4 | const parseDeparture = createParseArrOrDep(DEPARTURE); 5 | 6 | export { 7 | parseDeparture, 8 | }; 9 | -------------------------------------------------------------------------------- /parse/find-remarks.js: -------------------------------------------------------------------------------- 1 | import flatMap from 'lodash/flatMap.js'; 2 | 3 | // There are two kinds of notes: "remarks" (in `remL`) and HAFAS 4 | // Information Manager (HIM) notes (in `himL`). The former describe 5 | // the regular operating situation, e.g. "bicycles allows", whereas 6 | // the latter describe cancellations, construction work, etc. 7 | 8 | // hafas-client's naming scheme: 9 | // - hints: notes from `remL` for regular operation 10 | // - warnings: notes from `himL` for cancellations, construction, etc 11 | // - remarks: both "notes" and "warnings" 12 | 13 | const findRemarks = (refs) => { 14 | return flatMap(refs, (ref) => { 15 | return [ref.warning, ref.hint] 16 | .filter(rem => Boolean(rem)) 17 | .map(rem => [rem, ref]); 18 | }); 19 | }; 20 | 21 | export { 22 | findRemarks, 23 | }; 24 | -------------------------------------------------------------------------------- /parse/icon.js: -------------------------------------------------------------------------------- 1 | const parseIcon = (ctx, i) => { 2 | if (i.res === 'Empty') { 3 | return null; 4 | } 5 | const res = { 6 | type: i.res || null, 7 | title: i.text || i.txt || i.txtS || null, 8 | }; 9 | if (i.fg) { 10 | res.fgColor = i.fg; 11 | } 12 | if (i.bg) { 13 | res.bgColor = i.bg; 14 | } 15 | return res; 16 | }; 17 | 18 | export { 19 | parseIcon, 20 | }; 21 | -------------------------------------------------------------------------------- /parse/movement.js: -------------------------------------------------------------------------------- 1 | // todo: what is m.dirGeo? maybe the speed? 2 | // todo: what is m.stopL? 3 | // todo: what is m.proc? wut? 4 | // todo: what is m.pos? 5 | // todo: what is m.ani.dirGeo[n]? maybe the speed? 6 | // todo: what is m.ani.proc[n]? wut? 7 | const parseMovement = (ctx, m) => { // m = raw movement 8 | const {profile, opt} = ctx; 9 | 10 | const res = { 11 | direction: m.dirTxt 12 | ? profile.parseStationName(ctx, m.dirTxt) 13 | : null, 14 | tripId: m.jid || null, 15 | line: m.line || null, 16 | location: m.pos 17 | ? { 18 | type: 'location', 19 | latitude: m.pos.y / 1000000, 20 | longitude: m.pos.x / 1000000, 21 | } 22 | : null, 23 | // todo: stopL[0] is the first of the trip! -> filter out 24 | nextStopovers: 25 | m.stopL 26 | .filter(s => Boolean(s.location)) 27 | .map(s => profile.parseStopover(ctx, s, m.date)), 28 | frames: [], 29 | }; 30 | 31 | if (m.ani) { 32 | // todo: ani.dirGeo, ani.fLocX, ani.proc, ani.procAbs, ani.state, ani.stcOutputX 33 | 34 | if (Array.isArray(m.ani.mSec)) { 35 | for (let i = 0; i < m.ani.mSec.length; i++) { 36 | res.frames.push({ 37 | origin: m.ani.fromLocations[i] || null, 38 | destination: m.ani.toLocations[i] || null, 39 | t: m.ani.mSec[i], 40 | }); 41 | } 42 | } 43 | 44 | if (opt.polylines) { 45 | if (m.ani.poly) { 46 | res.polyline = profile.parsePolyline(ctx, m.ani.poly); 47 | } else if (m.ani.polyline) { 48 | res.polyline = m.ani.polyline; 49 | } 50 | } 51 | } 52 | 53 | return res; 54 | }; 55 | 56 | export { 57 | parseMovement, 58 | }; 59 | -------------------------------------------------------------------------------- /parse/nearby.js: -------------------------------------------------------------------------------- 1 | // todo: remarks 2 | // todo: lines 3 | // todo: what is s.pCls? 4 | // todo: what is s.wt? 5 | // todo: what is s.dur? 6 | 7 | const parseNearby = (ctx, n) => { // n = raw nearby location 8 | const res = ctx.profile.parseLocation(ctx, n); 9 | res.distance = n.dist; 10 | return res; 11 | }; 12 | 13 | export { 14 | parseNearby, 15 | }; 16 | -------------------------------------------------------------------------------- /parse/operator.js: -------------------------------------------------------------------------------- 1 | import slugg from 'slugg'; 2 | 3 | const parseOperator = (ctx, a) => { 4 | const name = a.name && a.name.trim(); 5 | if (!name) { 6 | return null; 7 | } 8 | return { 9 | type: 'operator', 10 | id: slugg(a.name), // todo: find a more reliable way 11 | name, 12 | }; 13 | }; 14 | 15 | export { 16 | parseOperator, 17 | }; 18 | -------------------------------------------------------------------------------- /parse/platform.js: -------------------------------------------------------------------------------- 1 | const parsePlatform = (ctx, platfS, platfR, cncl = false) => { 2 | let planned = platfS || null; 3 | let prognosed = platfR || null; 4 | 5 | if (cncl) { 6 | return { 7 | platform: null, 8 | plannedPlatform: planned, 9 | prognosedPlatform: prognosed, 10 | }; 11 | } 12 | return { 13 | platform: prognosed || planned, 14 | plannedPlatform: planned, 15 | }; 16 | }; 17 | 18 | export { 19 | parsePlatform, 20 | }; 21 | -------------------------------------------------------------------------------- /parse/products-bitmask.js: -------------------------------------------------------------------------------- 1 | const parseBitmask = ({profile}, bitmask) => { 2 | const res = {}; 3 | for (let product of profile.products) { 4 | res[product.id] = false; 5 | } 6 | 7 | const bits = bitmask.toString(2) 8 | .split('') 9 | .map(i => parseInt(i)) 10 | .reverse(); 11 | for (let i = 0; i < bits.length; i++) { 12 | if (!bits[i]) { 13 | continue; 14 | } // ignore `0` 15 | 16 | const product = profile.products.find(p => p.bitmasks.includes(Math.pow(2, i))); 17 | if (product) { 18 | res[product.id] = true; 19 | } 20 | } 21 | return res; 22 | }; 23 | 24 | export { 25 | parseBitmask, 26 | }; 27 | -------------------------------------------------------------------------------- /parse/prognosis-type.js: -------------------------------------------------------------------------------- 1 | const parsePrognosisType = (_, progType) => { 2 | return { 3 | PROGNOSED: 'prognosed', 4 | CALCULATED: 'calculated', 5 | // todo: are there more? 6 | }[progType] || null; 7 | }; 8 | 9 | export { 10 | parsePrognosisType, 11 | }; 12 | -------------------------------------------------------------------------------- /parse/scheduled-days.js: -------------------------------------------------------------------------------- 1 | import {DateTime} from 'luxon'; 2 | 3 | // todo: DRY with parse/date-time.js 4 | const parseDate = (date) => { 5 | const res = { 6 | year: parseInt(date.substr(-8, 4)), 7 | month: parseInt(date.substr(-4, 2)), 8 | day: parseInt(date.substr(-2, 2)), 9 | }; 10 | if (!Number.isInteger(res.year) || !Number.isInteger(res.month) || !Number.isInteger(res.day)) { 11 | throw new Error('invalid date format: ' + date); 12 | } 13 | return res; 14 | }; 15 | 16 | const parseScheduledDays = (ctx, sDays) => { 17 | const {profile} = ctx; 18 | 19 | // sDaysB is a bitmap mapping all days from fpB (first date of schedule) to fpE (last date in schedule). 20 | const {fpB, fpE} = ctx.res; 21 | if (!sDays.sDaysB || !fpB || !fpE) { 22 | return null; 23 | } 24 | 25 | const sDaysB = Buffer.from(sDays.sDaysB, 'hex'); 26 | const res = Object.create(null); 27 | 28 | const _fpB = parseDate(fpB); 29 | let d = DateTime.fromObject({ 30 | year: _fpB.year, month: _fpB.month, day: _fpB.day, 31 | hour: 0, minute: 0, second: 0, millisecond: 0, 32 | }, { 33 | zone: profile.timezone, 34 | locale: profile.locale, 35 | }); 36 | for (let b = 0; b < sDaysB.length; b++) { 37 | for (let i = 0; i < 8; i++) { 38 | res[d.toISODate()] = (sDaysB[b] & Math.pow(2, 7 - i)) > 0; 39 | d = d.plus({days: 1}); 40 | } 41 | } 42 | return res; 43 | }; 44 | 45 | export { 46 | parseScheduledDays, 47 | }; 48 | -------------------------------------------------------------------------------- /parse/trip.js: -------------------------------------------------------------------------------- 1 | import minBy from 'lodash/minBy.js'; 2 | import maxBy from 'lodash/maxBy.js'; 3 | import last from 'lodash/last.js'; 4 | 5 | const parseTrip = (ctx, t) => { // t = raw trip 6 | const {profile, opt} = ctx; 7 | 8 | // pretend the trip is a leg in a journey 9 | const fakeLeg = { 10 | type: 'JNY', 11 | dep: Array.isArray(t.stopL) 12 | ? minBy(t.stopL, 'idx') || t.stopL[0] 13 | : {}, 14 | arr: Array.isArray(t.stopL) 15 | ? maxBy(t.stopL, 'idx') || last(t.stopL) 16 | : {}, 17 | jny: t, 18 | }; 19 | 20 | // todo: this breaks if the trip starts on a different day 21 | // how does HAFAS do this? 22 | const today = () => profile.formatDate(profile, Date.now()); 23 | const date = t.date || today(); 24 | 25 | const trip = profile.parseJourneyLeg(ctx, fakeLeg, date); 26 | trip.id = trip.tripId; 27 | delete trip.tripId; 28 | delete trip.reachable; 29 | 30 | if (opt.scheduledDays) { 31 | const nrOfStopovers = t.stopL.length; 32 | // trips seem to use sDaysL[], journeys use sDays 33 | const sDaysL = Array.isArray(t.sDaysL) 34 | ? t.sDaysL 35 | : []; 36 | const matchingSDays = sDaysL.filter((sDays) => { 37 | return sDays.fLocIdx === 0 && sDays.tLocIdx === nrOfStopovers - 1; 38 | }); 39 | 40 | // if there are >1 sDays, we don't know how to interpret them 41 | const sDays = matchingSDays.length === 1 42 | ? matchingSDays[0] 43 | : null; 44 | // todo [breaking]: rename to scheduledDates 45 | trip.scheduledDays = profile.parseScheduledDays(ctx, sDays); 46 | } 47 | 48 | return trip; 49 | }; 50 | 51 | export { 52 | parseTrip, 53 | }; 54 | -------------------------------------------------------------------------------- /parse/when.js: -------------------------------------------------------------------------------- 1 | const parseWhen = (ctx, date, timeS, timeR, tzOffset, cncl = false) => { 2 | const parse = ctx.profile.parseDateTime; 3 | 4 | let planned = timeS 5 | ? parse(ctx, date, timeS, tzOffset, false) 6 | : null; 7 | let prognosed = timeR 8 | ? parse(ctx, date, timeR, tzOffset, false) 9 | : null; 10 | let delay = null; 11 | 12 | if (planned && prognosed) { 13 | const tPlanned = parse(ctx, date, timeS, tzOffset, true); 14 | const tPrognosed = parse(ctx, date, timeR, tzOffset, true); 15 | delay = Math.round((tPrognosed - tPlanned) / 1000); 16 | } 17 | 18 | if (cncl) { 19 | return { 20 | when: null, 21 | plannedWhen: planned, 22 | prognosedWhen: prognosed, 23 | delay, 24 | }; 25 | } 26 | return { 27 | when: prognosed || planned, 28 | plannedWhen: planned, 29 | delay, 30 | }; 31 | }; 32 | 33 | export { 34 | parseWhen, 35 | }; 36 | -------------------------------------------------------------------------------- /retry.js: -------------------------------------------------------------------------------- 1 | import retry from 'p-retry'; 2 | import {defaultProfile} from './lib/default-profile.js'; 3 | 4 | const retryDefaults = { 5 | retries: 3, 6 | factor: 3, 7 | minTimeout: 5 * 1000, 8 | }; 9 | 10 | const withRetrying = (profile, retryOpts = {}) => { 11 | retryOpts = Object.assign({}, retryDefaults, retryOpts); 12 | // https://github.com/public-transport/hafas-client/issues/76#issuecomment-574408717 13 | const {request} = {...defaultProfile, ...profile}; 14 | 15 | const retryingRequest = (...args) => { 16 | const attempt = () => { 17 | return request(...args) 18 | .catch((err) => { 19 | if (err.isHafasError) { 20 | throw err; 21 | } // continue 22 | if (err.code === 'ENOTFOUND') { // abort 23 | const abortErr = new retry.AbortError(err); 24 | Object.assign(abortErr, err); 25 | throw abortErr; 26 | } 27 | throw err; // continue 28 | }); 29 | }; 30 | return retry(attempt, retryOpts); 31 | }; 32 | 33 | return { 34 | ...profile, 35 | request: retryingRequest, 36 | }; 37 | }; 38 | 39 | export { 40 | withRetrying, 41 | }; 42 | -------------------------------------------------------------------------------- /test/bvg-arrivals.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/bvg/index.js'; 10 | const res = require('./fixtures/bvg-arrivals.json'); 11 | import {bvgArrivals as expected} from './fixtures/bvg-arrivals.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | direction: null, 18 | duration: 10, 19 | linesOfStops: true, 20 | remarks: true, 21 | stopovers: true, 22 | includeRelatedStations: true, 23 | when: '2021-10-28T10:35:00+02:00', 24 | products: {}, 25 | }; 26 | 27 | tap.test('parses an arrival correctly (BVG)', (t) => { 28 | const common = profile.parseCommon({profile, opt, res}); 29 | const ctx = {profile, opt, common, res}; 30 | const arrivals = res.jnyL.map(d => profile.parseArrival(ctx, d)); 31 | 32 | t.same(arrivals, expected); 33 | t.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /test/bvg-journey.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/bvg/index.js'; 10 | const res = require('./fixtures/bvg-journey.json'); 11 | import {bvgJourney as expected} from './fixtures/bvg-journey.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: null, 18 | via: null, 19 | stopovers: true, 20 | transfers: -1, 21 | transferTime: 0, 22 | accessibility: 'none', 23 | bike: false, 24 | tickets: true, 25 | polylines: true, 26 | remarks: true, 27 | walkingSpeed: 'normal', 28 | startWithWalking: true, 29 | scheduledDays: true, 30 | departure: '2019-08-18T14:03:50+02:00', 31 | products: {}, 32 | }; 33 | 34 | tap.test('parses a journey correctly (BVG)', (t) => { 35 | const common = profile.parseCommon({profile, opt, res}); 36 | const ctx = {profile, opt, common, res}; 37 | const journey = profile.parseJourney(ctx, res.outConL[0]); 38 | 39 | t.same(journey, expected); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/bvg-radar.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/bvg/index.js'; 10 | const res = require('./fixtures/bvg-radar.json'); 11 | import {bvgRadar as expected} from './fixtures/bvg-radar.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: 256, 18 | duration: 30, 19 | frames: 3, 20 | polylines: true, 21 | when: '2019-08-19T21:00:00+02:00', 22 | products: {}, 23 | }; 24 | 25 | tap.test('parses a radar() response correctly (BVG)', (t) => { 26 | const common = profile.parseCommon({profile, opt, res}); 27 | const ctx = {profile, opt, common, res}; 28 | const movements = res.jnyL.map(m => profile.parseMovement(ctx, m)); 29 | 30 | t.same(movements, expected); 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/bvg-trip-with-occupancy.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/bvg/index.js'; 10 | const res = require('./fixtures/bvg-trip-with-occupancy.json'); 11 | import {bvgTripWithOccupancy as expected} from './fixtures/bvg-trip-with-occupancy.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | stopovers: true, 18 | polyline: true, 19 | subStops: false, 20 | entrances: true, 21 | remarks: true, 22 | scheduledDays: true, 23 | when: '2021-10-28T09:28:00+02:00', 24 | }; 25 | 26 | tap.test('parses an trip with occupancy correctly (BVG)', (t) => { 27 | const common = profile.parseCommon({profile, opt, res}); 28 | const ctx = {profile, opt, common, res}; 29 | const trip = profile.parseTrip(ctx, res.journey); 30 | 31 | t.same(trip, expected); 32 | t.end(); 33 | }); 34 | -------------------------------------------------------------------------------- /test/db-arrivals.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/db/index.js'; 10 | const res = require('./fixtures/db-arrivals.json'); 11 | import {dbArrivals as expected} from './fixtures/db-arrivals.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | direction: null, 18 | duration: 10, 19 | linesOfStops: true, 20 | remarks: true, 21 | stopovers: true, 22 | includeRelatedStations: true, 23 | when: '2019-08-19T20:30:00+02:00', 24 | products: {}, 25 | }; 26 | 27 | tap.test('parses an arrival correctly (DB)', (t) => { 28 | const common = profile.parseCommon({profile, opt, res}); 29 | const ctx = {profile, opt, common, res}; 30 | const arrivals = res.jnyL.map(d => profile.parseArrival(ctx, d)); 31 | 32 | t.same(arrivals, expected); 33 | t.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /test/db-deps-with-destination.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/db/index.js'; 10 | const res = require('./fixtures/db-deps-with-destination.json'); 11 | 12 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 13 | const {profile} = client; 14 | 15 | const opt = { 16 | direction: null, 17 | duration: 10, 18 | linesOfStops: true, 19 | remarks: true, 20 | stopovers: true, 21 | includeRelatedStations: true, 22 | when: '2022-10-15T15:45:00+02:00', 23 | products: {}, 24 | }; 25 | 26 | tap.test('parses departure.destination correctly (DB)', (t) => { 27 | const common = profile.parseCommon({profile, opt, res}); 28 | const ctx = {profile, opt, common, res}; 29 | const departure = profile.parseDeparture(ctx, res.jnyL[0]); 30 | 31 | t.ok(departure.destination, 'missing departure.destination'); 32 | t.equal(departure.destination.type, 'stop', 'invalid departure.destination.type'); 33 | t.equal(departure.destination.id, '930200', 'invalid departure.destination.id'); 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/db-journey-2.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/db/index.js'; 10 | const res = require('./fixtures/db-journey-2.json'); 11 | import {dbJourney as expected} from './fixtures/db-journey-2.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: 4, 18 | via: null, 19 | stopovers: true, 20 | transfers: -1, 21 | transferTime: 0, 22 | accessibility: 'none', 23 | bike: false, 24 | tickets: true, 25 | polylines: true, 26 | remarks: true, 27 | walkingSpeed: 'normal', 28 | startWithWalking: true, 29 | scheduledDays: false, 30 | departure: '2020-11-16T10:00:00+01:00', 31 | products: {}, 32 | }; 33 | 34 | tap.test('parses a journey remarks without failing', (t) => { 35 | const common = profile.parseCommon({profile, opt, res}); 36 | const ctx = {profile, opt, common, res}; 37 | const journey = profile.parseJourney(ctx, res.outConL[2]); 38 | 39 | t.same(journey, expected); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/db-journey-additional-stopover.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | 5 | const require = createRequire(import.meta.url); 6 | 7 | import tap from 'tap'; 8 | 9 | import {createClient} from '../index.js'; 10 | import {profile as rawProfile} from '../p/db/index.js'; 11 | 12 | const resAdditionalStopover = require('./fixtures/db-journey-additional-stopover.json'); 13 | 14 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 15 | const {profile} = client; 16 | 17 | const opt = { 18 | results: 1, 19 | stopovers: true, 20 | }; 21 | 22 | // https://github.com/public-transport/hafas-client/issues/303 23 | 24 | tap.test('parses a journey having a leg with an additional stopover', (t) => { 25 | const common = profile.parseCommon({profile, opt, res: resAdditionalStopover}); 26 | const ctx = {profile, opt, common, res: resAdditionalStopover}; 27 | const journey = profile.parseJourney(ctx, resAdditionalStopover.outConL[0]); 28 | const stopovers = journey.legs[0].stopovers; 29 | 30 | const stopoverRegular = stopovers[6]; 31 | const stopoverAdditional = stopovers[7]; 32 | t.notOk('additional' in stopoverRegular, 'regular stopover has attribute additional'); 33 | t.equal(stopoverAdditional.additional, true, 'additional stopover doesn\'t have attribute additional'); 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/db-journey-polyline.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/db/index.js'; 10 | const res = require('./fixtures/db-journey-polyline.json'); 11 | import {dbJourneyPolyline as expected} from './fixtures/db-journey-polyline.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: null, 18 | via: null, 19 | stopovers: true, 20 | transfers: -1, 21 | transferTime: 0, 22 | accessibility: 'none', 23 | bike: false, 24 | tickets: true, 25 | polylines: true, 26 | remarks: false, 27 | walkingSpeed: 'normal', 28 | startWithWalking: true, 29 | scheduledDays: false, 30 | departure: '2020-07-27T10:00+02:00', 31 | products: {}, 32 | }; 33 | 34 | tap.test('parses a journey with an embedded polyline correctly', (t) => { 35 | const common = profile.parseCommon({profile, opt, res}); 36 | const ctx = {profile, opt, common, res}; 37 | const journey = profile.parseJourney(ctx, res.outConL[0]); 38 | 39 | t.same(journey, expected); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/db-journey.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/db/index.js'; 10 | const res = require('./fixtures/db-journey.json'); 11 | import {dbJourney as expected} from './fixtures/db-journey.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: null, 18 | via: null, 19 | stopovers: true, 20 | transfers: -1, 21 | transferTime: 0, 22 | accessibility: 'none', 23 | bike: false, 24 | tickets: true, 25 | polylines: true, 26 | remarks: true, 27 | walkingSpeed: 'normal', 28 | startWithWalking: true, 29 | scheduledDays: false, 30 | departure: '2020-04-10T20:33+02:00', 31 | products: {}, 32 | }; 33 | 34 | tap.test('parses a journey with a DEVI leg correctly (DB)', (t) => { 35 | const common = profile.parseCommon({profile, opt, res}); 36 | const ctx = {profile, opt, common, res}; 37 | const journey = profile.parseJourney(ctx, res.outConL[2]); 38 | 39 | t.same(journey, expected); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/db-stop.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/db/index.js'; 10 | const res = require('./fixtures/db-stop.json'); 11 | import {dbStop as expected} from './fixtures/db-stop.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | linesOfStops: false, // parse & expose lines at the stop/station? 18 | subStops: true, 19 | entrances: true, 20 | remarks: true, 21 | }; 22 | 23 | tap.test('parses a stop() response correctly (DB)', (t) => { 24 | const common = profile.parseCommon({profile, opt, res}); 25 | const ctx = {profile, opt, common, res}; 26 | const stop = profile.parseLocation(ctx, res.locL[0]); 27 | 28 | t.same(stop, expected); 29 | t.end(); 30 | }); 31 | -------------------------------------------------------------------------------- /test/e2e/bls.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as blsProfile} from '../../p/bls/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | import {testJourneysStationToAddress} from './lib/journeys-station-to-address.js'; 8 | 9 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 10 | const when = createWhen(blsProfile.timezone, blsProfile.locale, T_MOCK); 11 | 12 | const cfg = { 13 | when, 14 | stationCoordsOptional: false, 15 | products: blsProfile.products, 16 | minLatitude: 45.3184, 17 | minLongitude: 4.4604, 18 | maxLatitude: 47.2969, 19 | maxLongitude: 7.8607, 20 | }; 21 | 22 | const validate = createValidate(cfg); 23 | 24 | const client = createClient(blsProfile, 'public-transport/hafas-client:test'); 25 | 26 | const bernDennigkofengässli = '8590093'; 27 | 28 | tap.test('Dennigkofengässli to Schänzlihalde', async (t) => { 29 | const schänzlihalde = { 30 | type: 'location', 31 | id: '990017698', 32 | address: 'Bern, Schänzlihalde 17', 33 | latitude: 46.952835, 34 | longitude: 7.447527, 35 | }; 36 | 37 | const res = await client.journeys(bernDennigkofengässli, schänzlihalde, { 38 | results: 3, 39 | departure: when, 40 | }); 41 | 42 | await testJourneysStationToAddress({ 43 | test: t, 44 | res, 45 | validate, 46 | fromId: bernDennigkofengässli, 47 | to: schänzlihalde, 48 | }); 49 | t.end(); 50 | }); 51 | -------------------------------------------------------------------------------- /test/e2e/common.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createClient} from '../../index.js'; 4 | import {profile as vbbProfile} from '../../p/vbb/index.js'; 5 | 6 | const client = createClient(vbbProfile, 'public-transport/hafas-client:test'); 7 | 8 | tap.test('exposes the profile', (t) => { 9 | t.ok(client.profile); 10 | t.equal(client.profile.endpoint, vbbProfile.endpoint); 11 | t.end(); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/dart.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as dartProfile} from '../../p/dart/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(dartProfile.timezone, dartProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: dartProfile.products, 15 | maxLatitude: 45.391, 16 | maxLongitude: -88.176, 17 | minLatitude: 37.745, 18 | minLongitude: -96.877, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(dartProfile, 'public-transport/hafas-client:test'); 23 | 24 | const mlkJrParkwayAdamsAve = '951013488'; // MARTIN LUTHER KING JR PKWY/ADAMS AVE 25 | 26 | tap.test('locations named "martin luther kind adams"', async (t) => { 27 | const locations = await client.locations('martin luther kind adams'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === mlkJrParkwayAdamsAve || l.id === mlkJrParkwayAdamsAve; 32 | }), '"MARTIN LUTHER KING JR PKWY/ADAMS AVE" not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/ivb.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as ivbProfile} from '../../p/ivb/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(ivbProfile.timezone, ivbProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: ivbProfile.products, 15 | maxLatitude: 47.9504, 16 | maxLongitude: 17.0892, 17 | minLatitude: 45.7206, 18 | minLongitude: 7.8635, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(ivbProfile, 'public-transport/hafas-client:test'); 23 | 24 | const innsbruckGriesauweg = '476162400'; 25 | 26 | tap.test('locations named "griesauweg"', async (t) => { 27 | const locations = await client.locations('griesauweg'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === innsbruckGriesauweg || l.id === innsbruckGriesauweg; 32 | }), 'Innsbruck Griesauweg not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/kvb.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as kvbProfile} from '../../p/kvb/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(kvbProfile.timezone, kvbProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: kvbProfile.products, 15 | maxLatitude: 51.6479, 16 | maxLongitude: 7.8333, 17 | minLatitude: 50.3253, 18 | minLongitude: 6.2320, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(kvbProfile, 'public-transport/hafas-client:test'); 23 | 24 | const heumarkt = '900000001'; 25 | 26 | tap.test('locations named "heumarkt"', async (t) => { 27 | const locations = await client.locations('heumarkt'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === heumarkt || l.id === heumarkt; 32 | }), 'Heumarkt not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/lib/arrivals.js: -------------------------------------------------------------------------------- 1 | const testArrivals = async (cfg) => { 2 | const {test: t, res, validate} = cfg; 3 | const ids = cfg.ids || (cfg.id 4 | ? [cfg.id] 5 | : []); 6 | const {arrivals: arrs} = res; 7 | 8 | validate(t, res, 'arrivalsResponse', 'res'); 9 | 10 | for (let i = 0; i < arrs.length; i++) { 11 | let stop = arrs[i].stop; 12 | let name = `res.arrivals[${i}].stop`; 13 | if (stop.station) { 14 | stop = stop.station; 15 | name += '.station'; 16 | } 17 | 18 | t.ok( 19 | ids.includes(stop.id) 20 | || stop.station && ids.includes(stop.station.id), 21 | name + '.id is invalid', 22 | ); 23 | } 24 | 25 | // todo: move into arrivals validator 26 | t.same(arrs, arrs.sort((a, b) => t.when > b.when), 'res.arrivals must be sorted by .when'); 27 | }; 28 | 29 | export { 30 | testArrivals, 31 | }; 32 | -------------------------------------------------------------------------------- /test/e2e/lib/departures-in-direction.js: -------------------------------------------------------------------------------- 1 | const testDeparturesInDirection = async (cfg) => { 2 | const { 3 | test: t, 4 | fetchDepartures, 5 | fetchTrip, 6 | id, 7 | directionIds, 8 | when, 9 | validate, 10 | } = cfg; 11 | 12 | const res = await fetchDepartures(id, { 13 | direction: directionIds[0], 14 | when, 15 | }); 16 | const {departures: deps} = res; 17 | 18 | validate(t, res, 'departuresResponse', 'res'); 19 | 20 | for (let i = 0; i < deps.length; i++) { 21 | const dep = deps[i]; 22 | const name = `deps[${i}]`; 23 | 24 | const line = dep.line && dep.line.name; 25 | const {trip} = await fetchTrip(dep.tripId, line, { 26 | when, stopovers: true, 27 | }); 28 | t.ok(trip.stopovers.some(st => st.stop.station && directionIds.includes(st.stop.station.id) 29 | || directionIds.includes(st.stop.id), 30 | ), `trip ${dep.tripId} of ${name} has no stopover at ${directionIds.join('/')}`); 31 | } 32 | }; 33 | 34 | export { 35 | testDeparturesInDirection, 36 | }; 37 | -------------------------------------------------------------------------------- /test/e2e/lib/departures.js: -------------------------------------------------------------------------------- 1 | const testDepartures = async (cfg) => { 2 | const {test: t, res, validate} = cfg; 3 | const ids = cfg.ids || (cfg.id 4 | ? [cfg.id] 5 | : []); 6 | const {departures: deps} = res; 7 | 8 | validate(t, res, 'departuresResponse', 'res'); 9 | 10 | for (let i = 0; i < deps.length; i++) { 11 | let stop = deps[i].stop; 12 | let name = `res.departures[${i}].stop`; 13 | if (stop.station) { 14 | stop = stop.station; 15 | name += '.station'; 16 | } 17 | 18 | t.ok( 19 | ids.includes(stop.id) 20 | || stop.station && ids.includes(stop.station.id), 21 | `${name}.id is invalid (${stop.id}), must be one of ${ids.join('/')}`, 22 | ); 23 | } 24 | 25 | // todo: move into deps validator 26 | t.same(deps, deps.sort((a, b) => t.when > b.when), 'res.departures must be sorted by .when'); 27 | }; 28 | 29 | export { 30 | testDepartures, 31 | }; 32 | -------------------------------------------------------------------------------- /test/e2e/lib/journeys-fails-with-no-product.js: -------------------------------------------------------------------------------- 1 | const journeysFailsWithNoProduct = async (cfg) => { 2 | const { 3 | test: t, 4 | fetchJourneys, 5 | fromId, 6 | toId, 7 | when, 8 | products, 9 | } = cfg; 10 | 11 | const noProducts = Object.create(null); 12 | for (let p of products) { 13 | noProducts[p.id] = false; 14 | } 15 | 16 | await t.rejects(async () => { 17 | await fetchJourneys(fromId, toId, {departure: when, products: noProducts}); 18 | }); 19 | }; 20 | 21 | export { 22 | journeysFailsWithNoProduct, 23 | }; 24 | -------------------------------------------------------------------------------- /test/e2e/lib/journeys-station-to-address.js: -------------------------------------------------------------------------------- 1 | import isRoughlyEqual from 'is-roughly-equal'; 2 | 3 | const testJourneysStationToAddress = async (cfg) => { 4 | const {test: t, res, validate, fromId} = cfg; 5 | const {address, latitude, longitude} = cfg.to; 6 | 7 | validate(t, res, 'journeysResult', 'res'); 8 | const {journeys} = res; 9 | 10 | t.ok(journeys.length >= 3, 'journeys must have >=3 items'); 11 | for (let i = 0; i < journeys.length; i++) { 12 | const j = journeys[i]; 13 | 14 | const firstLeg = j.legs[0]; 15 | const orig = firstLeg.origin.station || firstLeg.origin; 16 | t.ok(orig.id, fromId); 17 | 18 | const d = j.legs[j.legs.length - 1].destination; 19 | const n = `res.journeys[0].legs[${i}].destination`; 20 | 21 | t.equal(d.type, 'location', n + '.type is invalid'); 22 | t.equal(d.address, address, n + '.address is invalid'); 23 | t.ok(isRoughlyEqual(0.0001, d.latitude, latitude), n + '.latitude is invalid'); 24 | t.ok(isRoughlyEqual(0.0001, d.longitude, longitude), n + '.longitude is invalid'); 25 | } 26 | }; 27 | 28 | export { 29 | testJourneysStationToAddress, 30 | }; 31 | -------------------------------------------------------------------------------- /test/e2e/lib/journeys-station-to-poi.js: -------------------------------------------------------------------------------- 1 | import isRoughlyEqual from 'is-roughly-equal'; 2 | 3 | const testJourneysStationToPoi = async (cfg) => { 4 | const {test: t, res, validate} = cfg; 5 | const fromIds = cfg.fromIds || (cfg.fromId 6 | ? [cfg.fromId] 7 | : []); 8 | const {id, name, latitude, longitude} = cfg.to; 9 | 10 | validate(t, res, 'journeysResult', 'res'); 11 | const {journeys} = res; 12 | 13 | t.ok(journeys.length >= 3, 'journeys must have >=3 items'); 14 | for (let i = 0; i < journeys.length; i++) { 15 | const j = journeys[i]; 16 | 17 | let o = j.legs[0].origin; 18 | const oN = 'res.journeys[0].legs[0].destination'; 19 | t.ok( 20 | fromIds.includes(o.id) 21 | || o.station && fromIds.includes(o.station.id), 22 | `invalid ${oN}.legs[0].origin`, 23 | ); 24 | 25 | let d = j.legs[j.legs.length - 1].destination; 26 | let dN = `res.journeys[${i}].legs[${j.legs.length - 1}].destination`; 27 | if (d.station) { 28 | d = d.station; 29 | dN += '.station'; 30 | } 31 | 32 | t.equal(d.type, 'location', dN + '.type is invalid'); 33 | t.equal(d.id, id, dN + '.id is invalid'); 34 | t.equal(d.name, name, dN + '.name is invalid'); 35 | t.ok(isRoughlyEqual(0.0001, d.latitude, latitude), dN + '.latitude is invalid'); 36 | t.ok(isRoughlyEqual(0.0001, d.longitude, longitude), dN + '.longitude is invalid'); 37 | } 38 | }; 39 | 40 | export { 41 | testJourneysStationToPoi, 42 | }; 43 | -------------------------------------------------------------------------------- /test/e2e/lib/journeys-station-to-station.js: -------------------------------------------------------------------------------- 1 | const testJourneysStationToStation = async (cfg) => { 2 | const {test: t, res, validate} = cfg; 3 | const fromIds = cfg.fromIds || (cfg.fromId 4 | ? [cfg.fromId] 5 | : []); 6 | const toIds = cfg.toIds || (cfg.toId 7 | ? [cfg.toId] 8 | : []); 9 | 10 | validate(t, res, 'journeysResult', 'res'); 11 | const {journeys} = res; 12 | 13 | t.ok(journeys.length >= 4, 'journeys must have >=4 items'); 14 | for (let i = 0; i < journeys.length; i++) { 15 | const j = journeys[i]; 16 | const n = `res.journeys[${i}]`; 17 | 18 | const o = j.legs[0].origin; 19 | const d = j.legs[j.legs.length - 1].destination; 20 | t.ok( 21 | fromIds.includes(o.id) 22 | || o.station && fromIds.includes(o.station.id), 23 | `invalid ${n}.legs[0].origin`, 24 | ); 25 | t.ok( 26 | toIds.includes(d.id) 27 | || d.station && toIds.includes(d.station.id), 28 | `invalid ${n}.legs[${j.legs.length - 1}].destination`, 29 | ); 30 | } 31 | }; 32 | 33 | export { 34 | testJourneysStationToStation, 35 | }; 36 | -------------------------------------------------------------------------------- /test/e2e/lib/journeys-walking-speed.js: -------------------------------------------------------------------------------- 1 | import isRoughlyEqual from 'is-roughly-equal'; 2 | 3 | const testJourneysWalkingSpeed = async (cfg) => { 4 | const {test: t, journeys, validate, from, to, when, products, minTimeDifference} = cfg; 5 | 6 | const {journeys: [journeyWithFastWalking]} = await journeys(from, to, { 7 | departure: when, 8 | results: 1, products, walkingSpeed: 'fast', 9 | }); 10 | const legWithFastWalking = journeyWithFastWalking.legs.find(l => l.walking); 11 | t.ok(legWithFastWalking, 'no walking leg in journey with fast walking'); 12 | 13 | const {journeys: [journeyWithSlowWalking]} = await journeys(from, to, { 14 | departure: when, 15 | results: 1, products, walkingSpeed: 'slow', 16 | }); 17 | const legWithSlowWalking = journeyWithSlowWalking.legs.find(l => l.walking); 18 | t.ok(legWithSlowWalking, 'no walking leg in journey with slow walking'); 19 | 20 | const fastDist = legWithFastWalking.distance; 21 | const slowDist = legWithSlowWalking.distance; 22 | t.ok(isRoughlyEqual(100, fastDist, slowDist), 'precondition failed'); 23 | const fastDur = new Date(legWithFastWalking.arrival) - new Date(legWithFastWalking.departure); 24 | const slowDur = new Date(legWithSlowWalking.arrival) - new Date(legWithSlowWalking.departure); 25 | t.notOk(isRoughlyEqual(minTimeDifference, fastDur, slowDur), 'walkingSpeed not applied'); 26 | t.end(); 27 | }; 28 | 29 | export { 30 | testJourneysWalkingSpeed, 31 | }; 32 | -------------------------------------------------------------------------------- /test/e2e/lib/journeys-with-detour.js: -------------------------------------------------------------------------------- 1 | const testJourneysWithDetour = async (cfg) => { 2 | const {test: t, res, validate, detourIds} = cfg; 3 | 4 | // We assume that going from A to B via C *without* detour is currently 5 | // impossible. We check if the routing engine computes a detour. 6 | 7 | validate(t, res, 'journeysResult', 'res'); 8 | const {journeys} = res; 9 | 10 | const leg = journeys[0].legs.some((leg) => { 11 | return leg.stopovers && leg.stopovers.some((st) => st.stop.station && detourIds.includes(st.stop.station.id) 12 | || detourIds.includes(st.stop.id), 13 | ); 14 | }); 15 | t.ok(leg, detourIds.join('/') + ' is not being passed'); 16 | }; 17 | 18 | export { 19 | testJourneysWithDetour, 20 | }; 21 | -------------------------------------------------------------------------------- /test/e2e/lib/lines.js: -------------------------------------------------------------------------------- 1 | const testLines = async (cfg) => { 2 | const { 3 | test: t, 4 | fetchLines, 5 | validate, 6 | query, 7 | } = cfg; 8 | 9 | const res = await fetchLines(query); 10 | const { 11 | lines, 12 | realtimeDataUpdatedAt, 13 | } = res; 14 | 15 | for (let i = 0; i < res.lines.length; i++) { 16 | const l = res.lines[i]; 17 | const name = `res.lines[${i}]`; 18 | validate(t, l, 'line', name); 19 | } 20 | 21 | validate(t, realtimeDataUpdatedAt, 'realtimeDataUpdatedAt', 'res.realtimeDataUpdatedAt'); 22 | }; 23 | 24 | export { 25 | testLines, 26 | }; 27 | -------------------------------------------------------------------------------- /test/e2e/lib/refresh-journey.js: -------------------------------------------------------------------------------- 1 | const simplify = j => j.legs.map(l => { 2 | return { 3 | origin: l.origin, 4 | destination: l.destination, 5 | departure: l.plannedDeparture || l.departure, 6 | arrival: l.plannedArrival || l.arrival, 7 | line: l.line, 8 | }; 9 | }); 10 | 11 | const testRefreshJourney = async (cfg) => { 12 | const { 13 | test: t, 14 | fetchJourneys, 15 | refreshJourney, 16 | validate, 17 | fromId, 18 | toId, 19 | when, 20 | } = cfg; 21 | 22 | const modelRes = await fetchJourneys(fromId, toId, { 23 | results: 1, departure: when, 24 | stopovers: false, 25 | }); 26 | validate(t, modelRes, 'journeysResult', 'modelRes'); 27 | const [model] = modelRes.journeys; 28 | 29 | // todo: move to journeys validator? 30 | t.equal(typeof model.refreshToken, 'string'); 31 | t.ok(model.refreshToken); 32 | 33 | const refreshedRes = await refreshJourney(model.refreshToken, { 34 | stopovers: false, 35 | }); 36 | validate(t, refreshedRes, 'refreshJourneyResult', 'refreshedRes'); 37 | const refreshed = refreshedRes.journey; 38 | 39 | t.same(simplify(refreshed), simplify(model)); 40 | }; 41 | 42 | export { 43 | testRefreshJourney, 44 | }; 45 | -------------------------------------------------------------------------------- /test/e2e/lib/remarks.js: -------------------------------------------------------------------------------- 1 | const WEEK = 7 * 24 * 60 * 60 * 1000; 2 | 3 | const testRemarks = async (cfg) => { 4 | const { 5 | test: t, 6 | fetchRemarks, 7 | validate, 8 | when, 9 | } = cfg; 10 | 11 | const res = await fetchRemarks({ 12 | results: 10, 13 | from: when, 14 | to: new Date(when + WEEK), 15 | }); 16 | const { 17 | remarks, 18 | realtimeDataUpdatedAt, 19 | } = res; 20 | 21 | for (let i = 0; i < res.remarks.length; i++) { 22 | const rem = res.remarks[i]; 23 | const name = `res.remarks[${i}]`; 24 | validate(t, rem, 'remark', name); 25 | } 26 | 27 | // most endpoints currently don't provide this info for remarks() 28 | if (realtimeDataUpdatedAt !== null) { 29 | validate(t, realtimeDataUpdatedAt, 'realtimeDataUpdatedAt', 'res.realtimeDataUpdatedAt'); 30 | } 31 | }; 32 | 33 | export { 34 | testRemarks, 35 | }; 36 | -------------------------------------------------------------------------------- /test/e2e/lib/server-info.js: -------------------------------------------------------------------------------- 1 | const testServerInfo = async (cfg) => { 2 | const { 3 | test: t, 4 | fetchServerInfo, 5 | } = cfg; 6 | 7 | const info = await fetchServerInfo(); 8 | t.ok(info, 'invalid info'); 9 | 10 | t.equal(typeof info.hciVersion, 'string', 'invalid info.hciVersion'); 11 | t.ok(info.hciVersion, 'invalid info.hciVersion'); 12 | 13 | t.equal(typeof info.timetableStart, 'string', 'invalid info.timetableStart'); 14 | t.ok(info.timetableStart, 'invalid info.timetableStart'); 15 | t.equal(typeof info.timetableEnd, 'string', 'invalid info.timetableEnd'); 16 | t.ok(info.timetableEnd, 'invalid info.timetableEnd'); 17 | 18 | t.equal(typeof info.serverTime, 'string', 'invalid info.serverTime'); 19 | t.notOk(Number.isNaN(Date.parse(info.serverTime)), 'invalid info.serverTime'); 20 | 21 | t.ok(Number.isInteger(info.realtimeDataUpdatedAt), 'invalid info.realtimeDataUpdatedAt'); 22 | t.ok(info.realtimeDataUpdatedAt > 0, 'invalid info.realtimeDataUpdatedAt'); 23 | }; 24 | 25 | export { 26 | testServerInfo, 27 | }; 28 | -------------------------------------------------------------------------------- /test/e2e/lib/validate-fptf-with.js: -------------------------------------------------------------------------------- 1 | import validateFptf from 'validate-fptf'; 2 | const {defaultValidators} = validateFptf; 3 | import anyOf from 'validate-fptf/lib/any-of.js'; 4 | 5 | import validators from './validators.js'; 6 | 7 | const createValidateFptfWith = (cfg, customValidators = {}) => { 8 | const val = Object.assign({}, defaultValidators); 9 | for (let key of Object.keys(validators)) { 10 | val[key] = validators[key](cfg); 11 | } 12 | Object.assign(val, customValidators); 13 | 14 | const validateFptfWith = (t, item, allowedTypes, name) => { 15 | if ('string' === typeof allowedTypes) { 16 | val[allowedTypes](val, item, name); 17 | } else { 18 | anyOf(allowedTypes, val, item, name); 19 | } 20 | t.pass(name + ' is valid'); 21 | }; 22 | return validateFptfWith; 23 | }; 24 | 25 | export { 26 | createValidateFptfWith, 27 | }; 28 | -------------------------------------------------------------------------------- /test/e2e/lib/vbb-bvg-validators.js: -------------------------------------------------------------------------------- 1 | import {products} from '../../../p/bvg/products.js'; 2 | 3 | import { 4 | createValidateStation, 5 | createValidateJourneyLeg, 6 | createValidateDeparture, 7 | createValidateMovement, 8 | } from './validators.js'; 9 | 10 | const createVbbBvgValidators = ({when}) => { 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products, 15 | }; 16 | 17 | // todo: coordsOptional = false 18 | const validateStation = createValidateStation(cfg); 19 | 20 | const validateJourneyLeg = createValidateJourneyLeg(cfg); 21 | 22 | const validateDeparture = createValidateDeparture(cfg); 23 | 24 | const validateMovement = createValidateMovement(cfg); 25 | 26 | return { 27 | cfg, 28 | validateStation, 29 | validateJourneyLeg, 30 | validateDeparture, 31 | validateMovement, 32 | }; 33 | }; 34 | 35 | export { 36 | createVbbBvgValidators, 37 | }; 38 | -------------------------------------------------------------------------------- /test/e2e/ooevv.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as oövvProfile} from '../../p/ooevv/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(oövvProfile.timezone, oövvProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: oövvProfile.products, 15 | maxLatitude: 49.7921, 16 | maxLongitude: 17.0892, 17 | minLatitude: 45.7206, 18 | minLongitude: 7.8635, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(oövvProfile, 'public-transport/hafas-client:test'); 23 | 24 | const linzTheatergasse = '444670100'; 25 | 26 | tap.test('locations named "theatergasse"', async (t) => { 27 | const locations = await client.locations('theatergasse'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === linzTheatergasse || l.id === linzTheatergasse; 32 | }), 'Linz Theatergasse not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/salzburg.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as salzburgProfile} from '../../p/salzburg/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(salzburgProfile.timezone, salzburgProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: salzburgProfile.products, 15 | maxLatitude: 47.9504, 16 | maxLongitude: 17.0892, 17 | minLatitude: 45.7206, 18 | minLongitude: 7.8635, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(salzburgProfile, 'public-transport/hafas-client:test'); 23 | 24 | const salzburgGaswerkgasse = '455001300'; 25 | 26 | tap.test('locations named "gaswerkgasse"', async (t) => { 27 | const locations = await client.locations('gaswerkgasse'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === salzburgGaswerkgasse || l.id === salzburgGaswerkgasse; 32 | }), 'Salzburg Gaswerkgasse not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/stv.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as stvProfile} from '../../p/stv/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(stvProfile.timezone, stvProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: stvProfile.products, 15 | maxLatitude: 47.9504, 16 | maxLongitude: 18.347, 17 | minLatitude: 46.127, 18 | minLongitude: 7.8635, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(stvProfile, 'public-transport/hafas-client:test'); 23 | 24 | const grazSonnenhang = '460413500'; 25 | 26 | tap.test('locations named "sonnenhang"', async (t) => { 27 | const locations = await client.locations('sonnenhang'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === grazSonnenhang || l.id === grazSonnenhang; 32 | }), 'Graz Sonnenhang not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/tpg.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as tpgProfile} from '../../p/tpg/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | import {testJourneysStationToAddress} from './lib/journeys-station-to-address.js'; 8 | 9 | const T_MOCK = 1668495600 * 1000; // 2022-11-15T08:00:00+01:00 10 | const when = createWhen(tpgProfile.timezone, tpgProfile.locale, T_MOCK); 11 | 12 | const cfg = { 13 | when, 14 | stationCoordsOptional: false, 15 | products: tpgProfile.products, 16 | minLatitude: 45.3184, 17 | minLongitude: 4.4604, 18 | maxLatitude: 47.2969, 19 | maxLongitude: 7.8607, 20 | }; 21 | 22 | const validate = createValidate(cfg); 23 | 24 | const client = createClient(tpgProfile, 'public-transport/hafas-client:test'); 25 | 26 | const moillebeau = '100451'; 27 | 28 | tap.test('Moillebeau to Cours des Bastions 10', async (t) => { 29 | const coursDesBastions10 = { 30 | type: 'location', 31 | id: '990001624', 32 | address: 'Cours des Bastions 10, 1205 Genève', 33 | latitude: 46.197768, 34 | longitude: 6.148046, 35 | }; 36 | 37 | const res = await client.journeys(moillebeau, coursDesBastions10, { 38 | results: 3, 39 | departure: when, 40 | }); 41 | 42 | await testJourneysStationToAddress({ 43 | test: t, 44 | res, 45 | validate, 46 | fromId: moillebeau, 47 | to: coursDesBastions10, 48 | }); 49 | t.end(); 50 | }); 51 | -------------------------------------------------------------------------------- /test/e2e/vor.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as vorProfile} from '../../p/vor/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(vorProfile.timezone, vorProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: vorProfile.products, 15 | maxLatitude: 47.9504, 16 | maxLongitude: 17.0892, 17 | minLatitude: 45.7206, 18 | minLongitude: 7.8635, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(vorProfile, 'public-transport/hafas-client:test'); 23 | 24 | const stPöltenLinzerTor = '431277900'; 25 | 26 | tap.test('locations named "linzer tor"', async (t) => { 27 | const locations = await client.locations('linzer tor'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === stPöltenLinzerTor || l.id === stPöltenLinzerTor; 32 | }), 'St. Pölten Linzer Tor not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/e2e/vvv.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createWhen} from './lib/util.js'; 4 | import {createClient} from '../../index.js'; 5 | import {profile as vvvProfile} from '../../p/vvv/index.js'; 6 | import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; 7 | 8 | const T_MOCK = 1671260400 * 1000; // 2022-12-17T08:00:00+01:00 9 | const when = createWhen(vvvProfile.timezone, vvvProfile.locale, T_MOCK); 10 | 11 | const cfg = { 12 | when, 13 | stationCoordsOptional: false, 14 | products: vvvProfile.products, 15 | maxLatitude: 47.9504, 16 | maxLongitude: 17.0892, 17 | minLatitude: 45.7206, 18 | minLongitude: 7.8635, 19 | }; 20 | const validate = createValidate(cfg); 21 | 22 | const client = createClient(vvvProfile, 'public-transport/hafas-client:test'); 23 | 24 | const bregenzLandeskrankenhaus = '480195700'; 25 | 26 | tap.test('locations named "bregenz krankenhaus"', async (t) => { 27 | const locations = await client.locations('bregenz krankenhaus'); 28 | 29 | validate(t, locations, 'locations', 'locations'); 30 | t.ok(locations.some((l) => { 31 | return l.station && l.station.id === bregenzLandeskrankenhaus || l.id === bregenzLandeskrankenhaus; 32 | }), 'Bregenz Landeskrankenhaus not found'); 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/fixtures/error-h9360.json: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.44", 3 | "ext": "BVG.1", 4 | "lang": "deu", 5 | "id": "3xmggksuiukkx6wx", 6 | "err": "OK", 7 | "graph": { 8 | "id": "standard", 9 | "index": 0 10 | }, 11 | "subGraph": { 12 | "id": "global", 13 | "index": 0 14 | }, 15 | "view": { 16 | "id": "standard", 17 | "index": 0, 18 | "type": "WGS84" 19 | }, 20 | "svcResL": [ 21 | { 22 | "meth": "StationBoard", 23 | "err": "H9360", 24 | "errTxt": "HAFAS Kernel: Date outside of the timetable period.", 25 | "errTxtOut": "Fehler bei der Datumseingabe oder Datum außerhalb der Fahrplanperiode (01.05.2022 - 10.12.2022)" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/error-location.json: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.44", 3 | "ext": "BVG.1", 4 | "lang": "deu", 5 | "id": "mmmxakseiuk9g6wg", 6 | "err": "OK", 7 | "graph": { 8 | "id": "standard", 9 | "index": 0 10 | }, 11 | "subGraph": { 12 | "id": "global", 13 | "index": 0 14 | }, 15 | "view": { 16 | "id": "standard", 17 | "index": 0, 18 | "type": "WGS84" 19 | }, 20 | "svcResL": [ 21 | { 22 | "meth": "StationBoard", 23 | "err": "LOCATION", 24 | "errTxt": "HCI Service: location missing or invalid", 25 | "errTxtOut": "Während der Suche ist ein interner Fehler aufgetreten" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/error-no-match.json: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.42", 3 | "lang": "deu", 4 | "id": "pz2cgmquimu5pm4k", 5 | "err": "OK", 6 | "graph": { 7 | "id": "standard", 8 | "index": 0 9 | }, 10 | "subGraph": { 11 | "id": "global", 12 | "index": 0 13 | }, 14 | "view": { 15 | "id": "standard", 16 | "index": 0, 17 | "type": "WGS84" 18 | }, 19 | "svcResL": [ 20 | { 21 | "meth": "JourneyMatch", 22 | "err": "NO_MATCH", 23 | "errTxt": "Nothing found.", 24 | "errTxtOut": "Während der Suche ist leider ein interner Fehler aufgetreten. Bitte wenden Sie sich an unsere Serviceauskunft unter Tel. 0421 596059." 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/error-parameter.json: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.44", 3 | "ext": "BVG.1", 4 | "lang": "deu", 5 | "id": "xmwk2kueimst964k", 6 | "err": "OK", 7 | "graph": { 8 | "id": "standard", 9 | "index": 0 10 | }, 11 | "subGraph": { 12 | "id": "global", 13 | "index": 0 14 | }, 15 | "view": { 16 | "id": "standard", 17 | "index": 0, 18 | "type": "WGS84" 19 | }, 20 | "svcResL": [ 21 | { 22 | "meth": "JourneyDetails", 23 | "err": "PARAMETER", 24 | "errTxt": "HCI Service: parameter invalid", 25 | "errTxtOut": "Während der Suche ist ein interner Fehler aufgetreten" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/insa-stop.js: -------------------------------------------------------------------------------- 1 | const insaStop = { 2 | type: 'stop', 3 | id: '7341', 4 | ids: { 5 | dhid: 'de:15003:7341', 6 | }, 7 | name: 'Magdeburg, S-Bahnhof SKET Industriepark', 8 | location: { 9 | type: 'location', 10 | id: '7341', 11 | latitude: 52.096849, 12 | longitude: 11.637444, 13 | }, 14 | products: { 15 | nationalExpress: false, 16 | national: false, 17 | regional: false, 18 | suburban: false, 19 | tram: false, 20 | bus: true, 21 | tourismTrain: false, 22 | }, 23 | }; 24 | 25 | export { 26 | insaStop, 27 | }; 28 | -------------------------------------------------------------------------------- /test/format/products-filter.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | import {formatProductsFilter as format} from '../../format/products-filter.js'; 3 | 4 | const products = [ 5 | { 6 | id: 'train', 7 | bitmasks: [1, 2], 8 | default: true, 9 | }, 10 | { 11 | id: 'bus', 12 | bitmasks: [4], 13 | default: true, 14 | }, 15 | { 16 | id: 'tram', 17 | bitmasks: [8, 32], 18 | default: false, 19 | }, 20 | ]; 21 | 22 | const ctx = { 23 | common: {}, 24 | opt: {}, 25 | profile: {products}, 26 | }; 27 | 28 | tap.test('formatProductsFilter works without customisations', (t) => { 29 | const expected = 1 | 2 | 4; 30 | const filter = {}; 31 | t.same(format(ctx, filter), { 32 | type: 'PROD', 33 | mode: 'INC', 34 | value: String(expected), 35 | }); 36 | t.end(); 37 | }); 38 | 39 | tap.test('formatProductsFilter works with customisations', (t) => { 40 | t.equal(Number(format(ctx, { 41 | bus: true, 42 | }).value), 1 | 2 | 4); 43 | t.equal(Number(format(ctx, { 44 | bus: false, 45 | }).value), 1 | 2); 46 | t.equal(Number(format(ctx, { 47 | tram: true, 48 | }).value), 1 | 2 | 4 | 8 | 32); 49 | t.end(); 50 | }); 51 | -------------------------------------------------------------------------------- /test/insa-stop.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/insa/index.js'; 10 | const res = require('./fixtures/insa-stop.json'); 11 | import {insaStop as expected} from './fixtures/insa-stop.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | linesOfStops: false, // parse & expose lines at the stop/station? 18 | remarks: true, 19 | }; 20 | 21 | tap.test('parses a stop() response correctly (INSA)', (t) => { 22 | const common = profile.parseCommon({profile, opt, res}); 23 | const ctx = {profile, opt, common, res}; 24 | const stop = profile.parseLocation(ctx, res.locL[0]); 25 | 26 | t.same(stop, expected); 27 | t.end(); 28 | }); 29 | -------------------------------------------------------------------------------- /test/mobiliteit-lu-line.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | 3 | import {createClient} from '../index.js'; 4 | import {profile as rawProfile} from '../p/mobiliteit-lu/index.js'; 5 | 6 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 7 | const {profile} = client; 8 | 9 | const opt = { 10 | linesOfStops: false, // parse & expose lines at the stop/station? 11 | remarks: true, 12 | }; 13 | 14 | tap.test('parses a line correctly (mobiliteit.lu)', (t) => { 15 | const rawLine = { 16 | pid: 'L::1::IC::B1303038328::IC_1303038328::*', 17 | name: 'IC 108', 18 | number: '108', 19 | icoX: 0, 20 | cls: 2, 21 | oprX: 0, 22 | prodCtx: { 23 | name: 'IC 108', 24 | num: '108', 25 | matchId: '108', 26 | catOut: 'IC ', 27 | catOutS: 'CIC', 28 | catOutL: 'IC ', 29 | catIn: 'CIC', 30 | catCode: '1', 31 | admin: 'C88---', 32 | }, 33 | }; 34 | 35 | const ctx = {profile, opt}; 36 | const stop = profile.parseLine(ctx, rawLine); 37 | 38 | t.same(stop, { 39 | type: 'line', 40 | id: 'ic-108', 41 | fahrtNr: '108', 42 | name: 'IC 108', 43 | public: true, 44 | adminCode: 'C88---', 45 | productName: 'IC', 46 | mode: 'train', 47 | product: 'national-train', 48 | }); 49 | t.end(); 50 | }); 51 | -------------------------------------------------------------------------------- /test/oebb-trip.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/oebb/index.js'; 10 | const res = require('./fixtures/oebb-trip.json'); 11 | import {oebbTrip as expected} from './fixtures/oebb-trip.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | stopovers: true, 18 | polyline: true, 19 | subStops: false, 20 | entrances: true, 21 | remarks: true, 22 | when: '2020-06-11T15:25:00+02:00', 23 | }; 24 | 25 | tap.test('parses a trip correctly (ÖBB)', (t) => { 26 | const common = profile.parseCommon({profile, opt, res}); 27 | const ctx = {profile, opt, common, res}; 28 | const trip = profile.parseTrip(ctx, res.journey); 29 | 30 | t.same(trip, expected); 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/parse/hint.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | import {parseHint as parse} from '../../parse/hint.js'; 3 | 4 | const ctx = { 5 | data: {}, 6 | opt: {}, 7 | profile: {}, 8 | }; 9 | 10 | tap.test('parses hints correctly', (t) => { 11 | const input = { 12 | type: 'A', 13 | code: 'bf', 14 | prio: 123, 15 | txtN: 'some text', 16 | }; 17 | const expected = { 18 | type: 'hint', 19 | code: 'bf', 20 | text: 'some text', 21 | }; 22 | 23 | t.same(parse(ctx, input), expected); 24 | t.same(parse(ctx, { 25 | ...input, type: 'I', 26 | }), expected); 27 | 28 | // alternative trip 29 | t.same(parse(ctx, { 30 | ...input, type: 'L', jid: 'trip id', 31 | }), { 32 | ...expected, type: 'status', code: 'alternative-trip', tripId: 'trip id', 33 | }); 34 | 35 | // type: M 36 | t.same(parse(ctx, { 37 | ...input, type: 'M', txtS: 'some summary', 38 | }), { 39 | ...expected, type: 'status', summary: 'some summary', 40 | }); 41 | 42 | // type: D 43 | for (const type of ['D', 'U', 'R', 'N', 'Y']) { 44 | t.same(parse(ctx, {...input, type}), { 45 | ...expected, type: 'status', 46 | }); 47 | } 48 | 49 | // .code via .icon 50 | t.same(parse(ctx, { 51 | ...input, code: null, icon: {type: 'cancel'}, 52 | }), {...expected, code: 'cancelled'}); 53 | 54 | // invalid 55 | t.equal(parse(ctx, {...input, type: 'X'}), null); 56 | 57 | t.end(); 58 | }); 59 | -------------------------------------------------------------------------------- /test/parse/icon.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | import {parseIcon as parse} from '../../parse/icon.js'; 3 | 4 | const ctx = { 5 | data: {}, 6 | opt: {}, 7 | profile: {}, 8 | }; 9 | 10 | tap.test('parses icons correctly', (t) => { 11 | const text = { 12 | res: 'BVG', 13 | text: 'Berliner Verkehrsbetriebe', 14 | }; 15 | t.same(parse(ctx, text), { 16 | type: 'BVG', 17 | title: 'Berliner Verkehrsbetriebe', 18 | }); 19 | 20 | const txtS = { 21 | res: 'PROD_BUS', 22 | txtS: '18', 23 | }; 24 | t.same(parse(ctx, txtS), { 25 | type: 'PROD_BUS', 26 | title: '18', 27 | }); 28 | 29 | const txt = { 30 | res: 'RBB', 31 | txt: 'Regionalbus Braunschweig GmbH', 32 | }; 33 | t.same(parse(ctx, txt), { 34 | type: 'RBB', 35 | title: 'Regionalbus Braunschweig GmbH', 36 | }); 37 | 38 | const noText = { 39 | res: 'attr_bike_r', 40 | }; 41 | t.same(parse(ctx, noText), { 42 | type: 'attr_bike_r', 43 | title: null, 44 | }); 45 | 46 | const withColor = { 47 | res: 'prod_sub_t', 48 | fg: { 49 | r: 255, 50 | g: 255, 51 | b: 255, 52 | a: 255, 53 | }, 54 | bg: { 55 | r: 0, 56 | g: 51, 57 | b: 153, 58 | a: 255, 59 | }, 60 | }; 61 | t.same(parse(ctx, withColor), { 62 | type: 'prod_sub_t', 63 | title: null, 64 | fgColor: {r: 255, g: 255, b: 255, a: 255}, 65 | bgColor: {r: 0, g: 51, b: 153, a: 255}, 66 | }); 67 | 68 | const empty = { 69 | res: 'Empty', 70 | }; 71 | t.equal(parse(ctx, empty), null); 72 | 73 | t.end(); 74 | }); 75 | -------------------------------------------------------------------------------- /test/parse/line.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | import omit from 'lodash/omit.js'; 3 | import {parseLine as parse} from '../../parse/line.js'; 4 | 5 | const profile = { 6 | products: [ 7 | {id: 'train', bitmasks: [1]}, 8 | {id: 'ferry', bitmasks: [2]}, 9 | {id: 'bus', bitmasks: [4, 8]}, 10 | ], 11 | }; 12 | const ctx = { 13 | data: {}, 14 | opt: {}, 15 | profile, 16 | }; 17 | 18 | tap.test('parses lines correctly', (t) => { 19 | const input = { 20 | line: 'foo line', 21 | prodCtx: { 22 | lineId: 'Foo ', 23 | num: 123, 24 | // HAFAS endpoints commonly have these padded admin codes. 25 | admin: 'foo---', 26 | }, 27 | }; 28 | const expected = { 29 | type: 'line', 30 | id: 'foo', 31 | fahrtNr: 123, 32 | name: 'foo line', 33 | public: true, 34 | adminCode: 'foo---', 35 | }; 36 | 37 | t.same(parse(ctx, input), expected); 38 | 39 | t.same(parse(ctx, { 40 | ...input, line: null, addName: input.line, 41 | }), expected); 42 | t.same(parse(ctx, { 43 | ...input, line: null, name: input.line, 44 | }), expected); 45 | 46 | // no prodCtx.lineId 47 | t.same(parse(ctx, { 48 | ...input, prodCtx: {...input.prodCtx, lineId: null}, 49 | }), { 50 | ...expected, id: 'foo-line', 51 | }); 52 | // no prodCtx 53 | t.same(parse(ctx, { 54 | ...input, prodCtx: undefined, 55 | }), { 56 | ...omit(expected, [ 57 | 'adminCode', 58 | ]), 59 | id: 'foo-line', 60 | fahrtNr: null, 61 | }); 62 | t.end(); 63 | }); 64 | -------------------------------------------------------------------------------- /test/parse/operator.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | import {parseOperator as parse} from '../../parse/operator.js'; 3 | 4 | const ctx = { 5 | data: {}, 6 | opt: {}, 7 | profile: {}, 8 | }; 9 | tap.test('parses an operator correctly', (t) => { 10 | const op = { 11 | name: 'Berliner Verkehrsbetriebe', 12 | icoX: 1, 13 | id: 'Berliner Verkehrsbetriebe', 14 | }; 15 | 16 | t.same(parse(ctx, op), { 17 | type: 'operator', 18 | id: 'berliner-verkehrsbetriebe', 19 | name: 'Berliner Verkehrsbetriebe', 20 | }); 21 | t.end(); 22 | }); 23 | -------------------------------------------------------------------------------- /test/parse/when.js: -------------------------------------------------------------------------------- 1 | import tap from 'tap'; 2 | import {parseWhen as parse} from '../../parse/when.js'; 3 | 4 | const profile = { 5 | parseDateTime: ({profile}, date, time, tzOffset, timestamp = false) => { 6 | if (timestamp) { 7 | return (String(date) + time - tzOffset * 60) * 1000; 8 | } 9 | return date + ':' + time; 10 | }, 11 | }; 12 | const ctx = { 13 | data: {}, 14 | opt: {}, 15 | profile, 16 | }; 17 | 18 | tap.test('parseWhen works correctly', (t) => { 19 | const date = '20190606'; 20 | const timeS = '163000'; 21 | const timeR = '163130'; 22 | const tzOffset = 120; 23 | const expected = { 24 | when: '20190606:163130', 25 | plannedWhen: '20190606:163000', 26 | delay: 130, // seconds 27 | }; 28 | 29 | t.same(parse(ctx, date, timeS, timeR, tzOffset), expected); 30 | 31 | // no realtime data 32 | t.same(parse(ctx, date, timeS, null, tzOffset), { 33 | ...expected, when: expected.plannedWhen, delay: null, 34 | }); 35 | 36 | // cancelled 37 | t.same(parse(ctx, date, timeS, timeR, tzOffset, true), { 38 | ...expected, 39 | when: null, 40 | prognosedWhen: expected.when, 41 | }); 42 | t.end(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/rejseplanen-trip.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/rejseplanen/index.js'; 10 | const res = require('./fixtures/rejseplanen-trip.json'); 11 | import {rejseplanenTrip as expected} from './fixtures/rejseplanen-trip.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | stopovers: true, 18 | polyline: true, 19 | subStops: false, 20 | entrances: true, 21 | remarks: true, 22 | when: '2021-11-12T17:30:00+02:00', 23 | }; 24 | 25 | tap.test('parses a trip correctly (Rejseplanen)', (t) => { 26 | const common = profile.parseCommon({profile, opt, res}); 27 | const ctx = {profile, opt, common, res}; 28 | const trip = profile.parseTrip(ctx, res.journey); 29 | 30 | t.same(trip, expected); 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/rsag-journey.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/rsag/index.js'; 10 | const res = require('./fixtures/rsag-journey.json'); 11 | import {rsagJourneys as expected} from './fixtures/rsag-journey.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | stopovers: false, 18 | tickets: false, 19 | polylines: false, 20 | subStops: true, 21 | entrances: true, 22 | remarks: false, 23 | products: {}, 24 | }; 25 | 26 | tap.test('parses a journey correctly (RSAG)', (t) => { 27 | const common = profile.parseCommon({profile, opt, res}); 28 | const ctx = {profile, opt, common, res}; 29 | const journey = profile.parseJourney(ctx, res.outConL[0]); 30 | 31 | t.same(journey, expected); 32 | t.end(); 33 | }); 34 | -------------------------------------------------------------------------------- /test/sncb-journey-with-chki.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/sncb/index.js'; 10 | const resWithChkiLeg = require('./fixtures/sncb-journey-with-chki.json'); 11 | 12 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 13 | const {profile} = client; 14 | 15 | const opt = { 16 | stopovers: false, 17 | tickets: false, 18 | polylines: false, 19 | subStops: false, 20 | entrances: false, 21 | remarks: true, 22 | }; 23 | 24 | tap.test('parses a journey with a CHKI leg (#267)', (t) => { 25 | const common = profile.parseCommon({profile, opt, res: resWithChkiLeg}); 26 | const ctx = {profile, opt, common, res: resWithChkiLeg}; 27 | const journey = profile.parseJourney(ctx, resWithChkiLeg.outConL[0]); 28 | 29 | const checkinLeg = journey.legs[0]; 30 | t.equal(checkinLeg.checkin, true, 'checkinLeg.checkin'); 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/throttle.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {withThrottling} from '../throttle.js'; 10 | import {profile as vbbProfile} from '../p/vbb/index.js'; 11 | const depsRes = require('./fixtures/vbb-departures.json'); 12 | 13 | const ua = 'public-transport/hafas-client:test'; 14 | const spichernstr = '900000042101'; 15 | 16 | tap.test('withThrottling works', {timeout: 3000}, (t) => { 17 | let calls = 0; 18 | const mockedRequest = async (ctx, userAgent, reqData) => { 19 | calls++; 20 | return { 21 | res: depsRes, 22 | common: ctx.profile.parseCommon({...ctx, res: depsRes}), 23 | }; 24 | }; 25 | 26 | const profile = withThrottling({ 27 | ...vbbProfile, 28 | request: mockedRequest, 29 | }, 2, 1000); 30 | const client = createClient(profile, ua); 31 | 32 | t.plan(3); 33 | for (let i = 0; i < 10; i++) { 34 | const p = client.departures(spichernstr, {duration: 1}); 35 | p.catch(() => {}); 36 | } 37 | 38 | setTimeout(() => t.equal(calls, 2), 500); 39 | setTimeout(() => t.equal(calls, 4), 1500); 40 | setTimeout(() => t.equal(calls, 6), 2500); 41 | }); 42 | -------------------------------------------------------------------------------- /test/vbb-departures.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/vbb/index.js'; 10 | const res = require('./fixtures/vbb-departures.json'); 11 | import {vbbDepartures as expected} from './fixtures/vbb-departures.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | direction: null, 18 | duration: 10, 19 | linesOfStops: true, 20 | remarks: true, 21 | stopovers: true, 22 | includeRelatedStations: true, 23 | when: '2021-10-12T17:42:00+02:00', 24 | products: {}, 25 | }; 26 | 27 | tap.test('parses a departure correctly (VBB)', (t) => { 28 | const common = profile.parseCommon({profile, opt, res}); 29 | const ctx = {profile, opt, common, res}; 30 | const departures = res.jnyL.map(d => profile.parseDeparture(ctx, d)); 31 | 32 | t.same(departures, expected); 33 | t.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /test/vbb-journeys.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/vbb/index.js'; 10 | const res = require('./fixtures/vbb-journeys.json'); 11 | import {vbbJourneys as expected} from './fixtures/vbb-journeys.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: null, 18 | via: null, 19 | stopovers: false, 20 | transfers: -1, 21 | transferTime: 0, 22 | accessibility: 'none', 23 | bike: false, 24 | walkingSpeed: 'normal', 25 | startWithWalking: true, 26 | tickets: false, 27 | polylines: false, 28 | subStops: true, 29 | entrances: true, 30 | remarks: true, 31 | scheduledDays: false, 32 | departure: '2020-12-07T13:29+01:00', 33 | products: {}, 34 | }; 35 | 36 | tap.test('parses a journeys() response correctly (VBB)', (t) => { 37 | const common = profile.parseCommon({profile, opt, res}); 38 | const ctx = {profile, opt, common, res}; 39 | const journeys = res.outConL.map(j => profile.parseJourney(ctx, j)); 40 | 41 | t.same(journeys, expected); 42 | t.end(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/vbb-on-demand-trip.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/vbb/index.js'; 10 | const res = require('./fixtures/vbb-on-demand-trip.json'); 11 | import {vbbOnDemandTrip as expected} from './fixtures/vbb-on-demand-trip.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | stopovers: true, 18 | polyline: true, 19 | subStops: false, 20 | entrances: true, 21 | remarks: true, 22 | when: '2021-10-24T16:07:00+02:00', 23 | }; 24 | 25 | tap.test('parses an on-demand trip correctly (VBB)', (t) => { 26 | const common = profile.parseCommon({profile, opt, res}); 27 | const ctx = {profile, opt, common, res}; 28 | const trip = profile.parseTrip(ctx, res.journey); 29 | 30 | t.same(trip, expected); 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/vsn-departures.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/vsn/index.js'; 10 | const res = require('./fixtures/vsn-departures.json'); 11 | import {vsnDepartures as expected} from './fixtures/vsn-departures.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | // results: null, 18 | // via: null, 19 | // stopovers: true, 20 | // transfers: -1, 21 | // transferTime: 0, 22 | // accessibility: 'none', 23 | // bike: false, 24 | // tickets: true, 25 | // polylines: true, 26 | // remarks: true, 27 | // walkingSpeed: 'normal', 28 | // startWithWalking: true, 29 | // scheduledDays: false, 30 | // departure: '2020-04-10T20:33+02:00', 31 | // products: {} 32 | }; 33 | 34 | tap.test('parses departures correctly (VSN)', (t) => { 35 | const common = profile.parseCommon({profile, opt, res}); 36 | const ctx = {profile, opt, common, res}; 37 | 38 | const dep = profile.parseDeparture(ctx, res.jnyL[0]); 39 | t.same(dep, expected); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/vsn-remarks.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module'; 4 | const require = createRequire(import.meta.url); 5 | 6 | import tap from 'tap'; 7 | 8 | import {createClient} from '../index.js'; 9 | import {profile as rawProfile} from '../p/vsn/index.js'; 10 | const res = require('./fixtures/vsn-remarks.json'); 11 | import {vsnRemarks as expected} from './fixtures/vsn-remarks.js'; 12 | 13 | const client = createClient(rawProfile, 'public-transport/hafas-client:test'); 14 | const {profile} = client; 15 | 16 | const opt = { 17 | results: 100, // maximum number of remarks 18 | // filter by time 19 | from: Date.now(), to: null, 20 | products: null, // filter by affected products 21 | }; 22 | 23 | tap.test('parses a remarks() response correctly (VSN)', (t) => { 24 | const common = profile.parseCommon({profile, opt, res}); 25 | const ctx = {profile, opt, common, res}; 26 | const warnings = res.msgL.map(w => profile.parseWarning(ctx, w)); 27 | 28 | t.same(warnings, expected); 29 | t.end(); 30 | }); 31 | -------------------------------------------------------------------------------- /throttle.js: -------------------------------------------------------------------------------- 1 | import throttle from 'p-throttle'; 2 | import {defaultProfile} from './lib/default-profile.js'; 3 | 4 | const withThrottling = (profile, limit = 5, interval = 1000) => { 5 | // https://github.com/public-transport/hafas-client/issues/76#issuecomment-574408717 6 | const {request} = {...defaultProfile, ...profile}; 7 | 8 | return { 9 | ...profile, 10 | request: throttle({limit, interval})(request), 11 | }; 12 | }; 13 | 14 | export { 15 | withThrottling, 16 | }; 17 | -------------------------------------------------------------------------------- /tools/debug-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "debug-cli", 4 | "version": "1.0.0", 5 | "type": "module", 6 | "bin": { 7 | "hafas-client-debug-cli": "./cli.js" 8 | }, 9 | "author": "Jannis R ", 10 | "license": "ISC", 11 | "engines": { 12 | "node": ">=16.17" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tools/endpoint-hci-version/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {parseArgs} from 'node:util'; 4 | import {createClient} from '../../index.js'; 5 | 6 | const { 7 | values: flags, 8 | positionals: args, 9 | } = parseArgs({ 10 | options: { 11 | help: { 12 | type: 'boolean', 13 | short: 'h', 14 | }, 15 | silent: { 16 | type: 'boolean', 17 | short: 's', 18 | }, 19 | }, 20 | strict: true, 21 | allowPositionals: true, 22 | }); 23 | 24 | if (flags.help) { 25 | process.stdout.write(` 26 | Usage: 27 | endpoint-hci-version 28 | Options: 29 | --silent -s Output just the version instead of a pretty 30 | represenation. 31 | Examples: 32 | endpoint-hci-version oebb 33 | \n`); 34 | process.exit(0); 35 | } 36 | 37 | const profileName = args[0]; 38 | const silent = flags.silent; 39 | (async () => { 40 | const {profile} = await import(`../../p/${profileName}/index.js`); 41 | 42 | const client = createClient( 43 | profile, 44 | 'hafas-client-endpoint-hci-version', 45 | ); 46 | 47 | const {hciVersion: v} = await client.serverInfo(); 48 | 49 | if ('string' !== typeof v || !v) { 50 | throw new Error('invalid/unexpected server response'); 51 | } 52 | if (silent) { 53 | console.log(v); 54 | } else { 55 | console.log(v + ' reported as the endpoint version ✔︎'); 56 | } 57 | })() 58 | .catch((err) => { 59 | console.error(err); 60 | process.exit(1); 61 | }); 62 | -------------------------------------------------------------------------------- /tools/endpoint-hci-version/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "endpoint-hci-version", 4 | "version": "1.0.0", 5 | "type": "module", 6 | "bin": { 7 | "hafas-client-endpoint-hci-version": "./cli.js" 8 | }, 9 | "author": "Jannis R ", 10 | "license": "ISC", 11 | "engines": { 12 | "node": ">=16.17" 13 | } 14 | } 15 | --------------------------------------------------------------------------------