mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-10-24 21:56:32 +03:00
Compare commits
20 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0120439e6 | ||
|
|
c2216120c9 | ||
|
|
bdbf4f3761 | ||
|
|
a1ab95c249 | ||
|
|
691f07b331 | ||
|
|
13351e3977 | ||
|
|
2f65f3d05d | ||
|
|
f31f56c00d | ||
|
|
31ef3ad56a | ||
|
|
5ac43bcfba | ||
|
|
5a2e4f5d13 | ||
|
|
2b1e816c7f | ||
|
|
b2d9a4e53e | ||
|
|
8e9d6ea67a | ||
|
|
d15369406d | ||
|
|
86f2302ad4 | ||
|
|
b59d7b3084 | ||
|
|
db4c03054a | ||
|
|
eac21d188b | ||
|
|
ad09f8b1be |
44 changed files with 9124 additions and 1501 deletions
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
|
@ -10,6 +10,9 @@ env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write # for OIDC-based publishing to npm
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-docker:
|
build-and-push-docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -53,7 +56,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
# for OIDC-based publishing to npm
|
||||||
|
- name: setup npm v11
|
||||||
|
run: npm install -g npm@11
|
||||||
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm publish --provenance --access public
|
- run: npm publish --provenance --access public
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
@ -95,6 +95,8 @@
|
||||||
"bitmasks",
|
"bitmasks",
|
||||||
"Blaschkoallee",
|
"Blaschkoallee",
|
||||||
"Blissestr",
|
"Blissestr",
|
||||||
|
"bmis",
|
||||||
|
"BMIS",
|
||||||
"BNWNZF",
|
"BNWNZF",
|
||||||
"Böhme",
|
"Böhme",
|
||||||
"BONVOYO",
|
"BONVOYO",
|
||||||
|
|
@ -195,6 +197,7 @@
|
||||||
"Fehrbelliner",
|
"Fehrbelliner",
|
||||||
"Fernbf",
|
"Fernbf",
|
||||||
"Fernverkehr",
|
"Fernverkehr",
|
||||||
|
"firmen",
|
||||||
"Flexpreis",
|
"Flexpreis",
|
||||||
"Flix",
|
"Flix",
|
||||||
"Fltr",
|
"Fltr",
|
||||||
|
|
@ -263,6 +266,7 @@
|
||||||
"Hohenzollerndamm",
|
"Hohenzollerndamm",
|
||||||
"Hüngheim",
|
"Hüngheim",
|
||||||
"IBNR",
|
"IBNR",
|
||||||
|
"identifikationsart",
|
||||||
"Ihren",
|
"Ihren",
|
||||||
"Ihrer",
|
"Ihrer",
|
||||||
"Informationen",
|
"Informationen",
|
||||||
|
|
@ -610,6 +614,7 @@
|
||||||
"Zoologischer",
|
"Zoologischer",
|
||||||
"Zuege",
|
"Zuege",
|
||||||
"zugart",
|
"zugart",
|
||||||
|
"Zugehoerigkeit",
|
||||||
"zugattrib",
|
"zugattrib",
|
||||||
"zugattribute",
|
"zugattribute",
|
||||||
"Zuges",
|
"Zuges",
|
||||||
|
|
@ -627,7 +632,12 @@
|
||||||
"Intervalle",
|
"Intervalle",
|
||||||
"tagesbest",
|
"tagesbest",
|
||||||
"dbbahnhof",
|
"dbbahnhof",
|
||||||
"cancelation"
|
"Deutschlandticket",
|
||||||
|
"fahrverguenstigungen",
|
||||||
|
"cancelation",
|
||||||
|
"MOTIS",
|
||||||
|
"motis",
|
||||||
|
"Berechnung"
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"docs/dumps/**",
|
"docs/dumps/**",
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ Notes:
|
||||||
* routing-search returns polylines (!)
|
* routing-search returns polylines (!)
|
||||||
|
|
||||||
## Vendo/Movas Navigator API
|
## Vendo/Movas Navigator API
|
||||||
https://app.vendo.noncd.db.de/mob/
|
https://app.vendo.noncd.db.de/mob/ and/or https://app.services-bahn.de/
|
||||||
|
|
||||||
EPs:
|
EPs:
|
||||||
* bahnhofstafel/abfahrt
|
* bahnhofstafel/abfahrt
|
||||||
|
|
|
||||||
17
docs/dumps/PCAPdroid_21_Oct_17_55_08_routesearch.txt
Normal file
17
docs/dumps/PCAPdroid_21_Oct_17_55_08_routesearch.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
POST /mob/angebote/fahrplan HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
X-Correlation-ID: 599238b3-d1e0-43a0-9534-ebc0dbf30b72_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: 48cce61a-1b80-4dc4-8818-50bbb38ef54d
|
||||||
|
Content-Type: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
Content-Length: 822
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
|
{"autonomeReservierung":false,"einstiegsTypList":["STANDARD"],"fahrverguenstigungen":{"deutschlandTicketVorhanden":true,"nurDeutschlandTicketVerbindungen":false},"klasse":"KLASSE_2","reiseHin":{"wunsch":{"abgangsLocationId":"A\u003d1@O\u003dFrankfurt(Main)Hbf@X\u003d8662833@Y\u003d50106682@U\u003d80@L\u003d8000105@B\u003d1@p\u003d1760568530@i\u003dU×008011068@","alternativeHalteBerechnung":true,"verkehrsmittel":["ALL"],"zeitWunsch":{"reiseDatum":"2025-10-21T17:47:34.688151+02:00","zeitPunktArt":"ABFAHRT"},"zielLocationId":"A\u003d1@O\u003dBerlin Hbf@X\u003d13369549@Y\u003d52525589@U\u003d80@L\u003d8011160@B\u003d1@p\u003d1760568530@i\u003dU×008065969@"}},"reisendenProfil":{"reisende":[{"ermaessigungen":["KEINE_ERMAESSIGUNG KLASSENLOS"],"reisendenTyp":"ERWACHSENER"}]},"reservierungsKontingenteVorhanden":false}
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_17_55_26_routesearch.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_17_55_26_routesearch.txt
Normal file
Binary file not shown.
17
docs/dumps/PCAPdroid_21_Oct_17_58_35_deticket.txt
Normal file
17
docs/dumps/PCAPdroid_21_Oct_17_58_35_deticket.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
POST /mob/angebote/fahrplan HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
X-Correlation-ID: 599238b3-d1e0-43a0-9534-ebc0dbf30b72_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: 1f809128-7b90-411d-8fc4-99a329bc3bf6
|
||||||
|
Content-Type: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
Content-Length: 821
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
|
{"autonomeReservierung":false,"einstiegsTypList":["STANDARD"],"fahrverguenstigungen":{"deutschlandTicketVorhanden":true,"nurDeutschlandTicketVerbindungen":true},"klasse":"KLASSE_2","reiseHin":{"wunsch":{"abgangsLocationId":"A\u003d1@O\u003dFrankfurt(Main)Hbf@X\u003d8662833@Y\u003d50106682@U\u003d80@L\u003d8000105@B\u003d1@p\u003d1760568530@i\u003dU×008011068@","alternativeHalteBerechnung":true,"verkehrsmittel":["ALL"],"zeitWunsch":{"reiseDatum":"2025-10-21T17:47:34.688151+02:00","zeitPunktArt":"ABFAHRT"},"zielLocationId":"A\u003d1@O\u003dBerlin Hbf@X\u003d13369549@Y\u003d52525589@U\u003d80@L\u003d8011160@B\u003d1@p\u003d1760568530@i\u003dU×008065969@"}},"reisendenProfil":{"reisende":[{"ermaessigungen":["KEINE_ERMAESSIGUNG KLASSENLOS"],"reisendenTyp":"ERWACHSENER"}]},"reservierungsKontingenteVorhanden":false}
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_17_58_44_deticket.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_17_58_44_deticket.txt
Normal file
Binary file not shown.
17
docs/dumps/PCAPdroid_21_Oct_18_18_41_locsearch.txt
Normal file
17
docs/dumps/PCAPdroid_21_Oct_18_18_41_locsearch.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
POST /mob/location/search HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.location.v3+json
|
||||||
|
X-Correlation-ID: 599238b3-d1e0-43a0-9534-ebc0dbf30b72_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: a0f760ec-0443-4209-b564-e714ea548a16
|
||||||
|
Content-Type: application/x.db.vendo.mob.location.v3+json
|
||||||
|
Content-Length: 45
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
|
{"locationTypes":["ALL"],"searchTerm":"test"}
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_18_18_54_locsearch.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_18_18_54_locsearch.txt
Normal file
Binary file not shown.
17
docs/dumps/PCAPdroid_21_Oct_18_19_55_board.txt
Normal file
17
docs/dumps/PCAPdroid_21_Oct_18_19_55_board.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
POST /mob/bahnhofstafel/abfahrt HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.bahnhofstafeln.v2+json
|
||||||
|
X-Correlation-ID: 9c9405f1-4792-4ec0-ba1f-ca231a15ac36_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: a8b79643-ab22-472d-ab6e-40d5b888b078
|
||||||
|
Content-Type: application/x.db.vendo.mob.bahnhofstafeln.v2+json
|
||||||
|
Content-Length: 401
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
|
{"anfragezeit":"17:47","datum":"2025-10-21","ursprungsBahnhofId":"A\u003d1@O\u003dTessin@X\u003d12462618@Y\u003d54032020@U\u003d81@L\u003d8013106@B\u003d1@p\u003d1760557009@i\u003dU×008027109@","verkehrsmittel":["HOCHGESCHWINDIGKEITSZUEGE","INTERCITYUNDEUROCITYZUEGE","INTERREGIOUNDSCHNELLZUEGE","NAHVERKEHRSONSTIGEZUEGE","SBAHNEN","BUSSE","SCHIFFE","UBAHN","STRASSENBAHN","ANRUFPFLICHTIGEVERKEHRE"]}
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_18_20_05_board.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_18_20_05_board.txt
Normal file
Binary file not shown.
15
docs/dumps/PCAPdroid_21_Oct_18_20_53_zuglauf.txt
Normal file
15
docs/dumps/PCAPdroid_21_Oct_18_20_53_zuglauf.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
GET /mob/zuglauf/2%7C%23VN%231%23ST%231760568530%23PI%230%23ZI%23237216%23TA%230%23DA%23211025%231S%238013106%231T%231814%23LS%238010381%23LT%232015%23PU%2380%23RT%231%23CA%23RB%23ZE%2313134%23ZB%23RB%20%20%20%20%20%20%20%20%20%2013134%23PC%233%23FR%238013106%23FT%231814%23TO%238010381%23TT%232015%23 HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.zuglauf.v2+json
|
||||||
|
Content-Type: application/x.db.vendo.mob.zuglauf.v2+json
|
||||||
|
X-Correlation-ID: 9c9405f1-4792-4ec0-ba1f-ca231a15ac36_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: 1a70c54c-b0a8-4d8a-a23b-d6da23a50423
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_18_21_01_zuglauf.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_18_21_01_zuglauf.txt
Normal file
Binary file not shown.
17
docs/dumps/PCAPdroid_21_Oct_18_23_03_reservierung.txt
Normal file
17
docs/dumps/PCAPdroid_21_Oct_18_23_03_reservierung.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
POST /mob/angebote/recon/autonomereservierung HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
X-Correlation-ID: 232a47fb-0827-4e11-aa2e-d141c718f63f_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: 0b0fdc2d-be90-4e00-b5ed-33bf80fe2e23
|
||||||
|
Content-Type: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
Content-Length: 1862
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
|
{"einstiegsTypList":["STANDARD"],"klasse":"KLASSE_2","reisendenProfil":{"reisende":[{"ermaessigungen":["KEINE_ERMAESSIGUNG KLASSENLOS"],"reisendenTyp":"ERWACHSENER"}]},"reservierungsKontingenteVorhanden":false,"suchParameter":{"reisewunschHin":{"abgangsLocationId":"A\u003d1@O\u003dFrankfurt(Main)Hbf@X\u003d8662833@Y\u003d50106682@U\u003d80@L\u003d8000105@i\u003dU×008011068@","alternativeHalteBerechnung":true,"verkehrsmittel":["ALL"],"zeitWunsch":{"reiseDatum":"2025-10-21T17:47:34.688151+02:00","zeitPunktArt":"ABFAHRT"},"zielLocationId":"A\u003d1@O\u003dBerlin Hbf@X\u003d13369549@Y\u003d52525589@U\u003d80@L\u003d8011160@i\u003dU×008065969@"}},"verbindungHin":{"kontext":"¶HKI¶T$A\u003d1@O\u003dFrankfurt(Main)Hbf@X\u003d8662833@Y\u003d50106682@L\u003d8000105@a\u003d128@$A\u003d1@O\u003dBerlin Hbf@X\u003d13369549@Y\u003d52525589@L\u003d8011160@a\u003d128@$202510211802$202510212203$ICE 1032$$1$$$$$$¶KC¶#VE#2#CF#100#CA#0#CM#0#SICT#0#AM#81#AM2#0#RT#7#¶KCC¶I1ZFIzEjRVJHIzEjSElOIzAjRUNLIzQ1MDM2Mnw0NTAzNjJ8NDUwNjAzfDQ1MDYwM3wwfDB8NDg1fDQ1MDM0NHwxfDB8MTA1MHwwfDB8LTIxNDc0ODM2NDgjR0FNIzIxMTAyNTE4MDIjClojVk4jMSNTVCMxNzYwNTY4NTMwI1BJIzAjWkkjMjExNTc4I1RBIzAjREEjMjExMDI1IzFTIzgwMDAxMDUjMVQjMTgwMiNMUyM4MDEwMjU1I0xUIzIyMTUjUFUjODAjUlQjMSNDQSNJQ0UjWkUjMTAzMiNaQiNJQ0UgICAgICAgICAgMTAzMiNQQyMwI0ZSIzgwMDAxMDUjRlQjMTgwMiNUTyM4MDExMTYwI1RUIzIyMDMj¶KRCC¶#VE#1#¶SC¶1_H4sIAAAAAAACA32P4UrDMBSFX0XuL4U6knTJ0kKhdqWoTDfGJor4I67prHbtTFOxlD6HD+SLmbaKgiKBkHM493w3DbxIBS7gER+DBfJVGxEGo6twhIkxlHwGt4G82kXgUqt7BOAiC4pKh0JLkyaIUIwIht5cpbvOxJPxBCFjJX3DMbbgMa+jTKsZuLcN6HrfxRbLeWhCuyLu1Nnl1IgXkVV9BSI2tHf9UtOH7VBsyLHcz4rNUJOlsUmeeNife5ES+VNSKX14IdL86PQ+8a89zhjhtu3feBRhxBgn/trjyJ+ZCxmH+oEZ3nt4whBlnNrIT731+xtCHGEzwH2zUamHn0b9OkKpP/mBVFmaHwxcbNvMoWOnAxNzKHe+wRhjhv4BM+ow5zd4K3W4XIGrVSV7tSiy2iDlD+u8qFQu66Co8rgENxFZ+ZkVZZmlpf7Kyk2xEErsTKhp2/YDhSDWlQgCAAA\u003d"}}
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_18_23_16_reservierung.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_18_23_16_reservierung.txt
Normal file
Binary file not shown.
17
docs/dumps/PCAPdroid_21_Oct_18_23_42_recon.txt
Normal file
17
docs/dumps/PCAPdroid_21_Oct_18_23_42_recon.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
POST /mob/angebote/recon HTTP/1.1
|
||||||
|
Accept: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
X-Correlation-ID: 232a47fb-0827-4e11-aa2e-d141c718f63f_339465f1-2b99-4c6e-b739-c8ad0efed49a
|
||||||
|
X-Device-Os-Name: Android
|
||||||
|
X-Device-Os-Version: 32
|
||||||
|
X-Device-Model: Google Pixel 3a
|
||||||
|
X-App-Version: 25.18.2
|
||||||
|
User-Agent: DBNavigator/Android/25.18.2
|
||||||
|
Accept-Language: en,de
|
||||||
|
X-INSTANA-ANDROID: 746aa2dd-bc0c-450c-a444-f6036963caf9
|
||||||
|
Content-Type: application/x.db.vendo.mob.verbindungssuche.v9+json
|
||||||
|
Content-Length: 1962
|
||||||
|
Host: app.services-bahn.de
|
||||||
|
Connection: Keep-Alive
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
|
||||||
|
{"einstiegsTypList":["STANDARD"],"fahrverguenstigungen":{"deutschlandTicketVorhanden":true,"nurDeutschlandTicketVerbindungen":false},"klasse":"KLASSE_2","reisendenProfil":{"reisende":[{"ermaessigungen":["KEINE_ERMAESSIGUNG KLASSENLOS"],"reisendenTyp":"ERWACHSENER"}]},"reservierungsKontingenteVorhanden":false,"suchParameter":{"reisewunschHin":{"abgangsLocationId":"A\u003d1@O\u003dFrankfurt(Main)Hbf@X\u003d8662833@Y\u003d50106682@U\u003d80@L\u003d8000105@i\u003dU×008011068@","alternativeHalteBerechnung":true,"verkehrsmittel":["ALL"],"zeitWunsch":{"reiseDatum":"2025-10-21T17:47:34.688151+02:00","zeitPunktArt":"ABFAHRT"},"zielLocationId":"A\u003d1@O\u003dBerlin Hbf@X\u003d13369549@Y\u003d52525589@U\u003d80@L\u003d8011160@i\u003dU×008065969@"}},"verbindungHin":{"kontext":"¶HKI¶T$A\u003d1@O\u003dFrankfurt(Main)Hbf@X\u003d8662833@Y\u003d50106682@L\u003d8000105@a\u003d128@$A\u003d1@O\u003dBerlin Hbf@X\u003d13369549@Y\u003d52525589@L\u003d8011160@a\u003d128@$202510211802$202510212203$ICE 1032$$1$$$$$$¶KC¶#VE#2#CF#100#CA#0#CM#0#SICT#0#AM#81#AM2#0#RT#7#¶KCC¶I1ZFIzEjRVJHIzEjSElOIzAjRUNLIzQ1MDM2Mnw0NTAzNjJ8NDUwNjAzfDQ1MDYwM3wwfDB8NDg1fDQ1MDM0NHwxfDB8MTA1MHwwfDB8LTIxNDc0ODM2NDgjR0FNIzIxMTAyNTE4MDIjClojVk4jMSNTVCMxNzYwNTY4NTMwI1BJIzAjWkkjMjExNTc4I1RBIzAjREEjMjExMDI1IzFTIzgwMDAxMDUjMVQjMTgwMiNMUyM4MDEwMjU1I0xUIzIyMTUjUFUjODAjUlQjMSNDQSNJQ0UjWkUjMTAzMiNaQiNJQ0UgICAgICAgICAgMTAzMiNQQyMwI0ZSIzgwMDAxMDUjRlQjMTgwMiNUTyM4MDExMTYwI1RUIzIyMDMj¶KRCC¶#VE#1#¶SC¶1_H4sIAAAAAAACA32P4UrDMBSFX0XuL4U6knTJ0kKhdqWoTDfGJor4I67prHbtTFOxlD6HD+SLmbaKgiKBkHM493w3DbxIBS7gER+DBfJVGxEGo6twhIkxlHwGt4G82kXgUqt7BOAiC4pKh0JLkyaIUIwIht5cpbvOxJPxBCFjJX3DMbbgMa+jTKsZuLcN6HrfxRbLeWhCuyLu1Nnl1IgXkVV9BSI2tHf9UtOH7VBsyLHcz4rNUJOlsUmeeNife5ES+VNSKX14IdL86PQ+8a89zhjhtu3feBRhxBgn/trjyJ+ZCxmH+oEZ3nt4whBlnNrIT731+xtCHGEzwH2zUamHn0b9OkKpP/mBVFmaHwxcbNvMoWOnAxNzKHe+wRhjhv4BM+ow5zd4K3W4XIGrVSV7tSiy2iDlD+u8qFQu66Co8rgENxFZ+ZkVZZmlpf7Kyk2xEErsTKhp2/YDhSDWlQgCAAA\u003d"}}
|
||||||
BIN
docs/dumps/PCAPdroid_21_Oct_18_23_50_recon.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_18_23_50_recon.txt
Normal file
Binary file not shown.
BIN
docs/dumps/PCAPdroid_21_Oct_18_24_19_routesearch_delay.txt
Normal file
BIN
docs/dumps/PCAPdroid_21_Oct_18_24_19_routesearch_delay.txt
Normal file
Binary file not shown.
|
|
@ -81,6 +81,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
firstClass: false, // first or second class for tickets
|
firstClass: false, // first or second class for tickets
|
||||||
loyaltyCard: null, // BahnCards etc., see below
|
loyaltyCard: null, // BahnCards etc., see below
|
||||||
language: 'en', // language to get results in
|
language: 'en', // language to get results in
|
||||||
|
bmisNumber: null, // 7-digit BMIS number for business customer rates
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -316,6 +317,27 @@ hafas.journeys(from, to, {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Using the `bmisNumber` option
|
||||||
|
|
||||||
|
bahn.business customers with a BMIS number can get their corporate rates and corporate tariffs by providing their 7-digit BMIS number:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Option 1: Using the bmisNumber parameter directly
|
||||||
|
await client.journeys(from, to, {
|
||||||
|
bmisNumber: '1234567' // Your 7-digit BMIS number
|
||||||
|
})
|
||||||
|
|
||||||
|
// Option 2: Using the createBusinessClient helper function
|
||||||
|
import {createBusinessClient} from 'db-vendo-client'
|
||||||
|
import {profile as dbProfile} from 'db-vendo-client/p/db/index.js'
|
||||||
|
|
||||||
|
const businessClient = createBusinessClient(dbProfile, userAgent, '1234567')
|
||||||
|
// Now all journey searches will automatically include the BMIS number
|
||||||
|
await businessClient.journeys(from, to)
|
||||||
|
```
|
||||||
|
|
||||||
|
When a BMIS number is provided, the request will include a `firmenZugehoerigkeit` object with the BMIS number and identification type, allowing business customers to access their special rates and conditions.
|
||||||
|
|
||||||
## The `routingMode` option
|
## The `routingMode` option
|
||||||
|
|
||||||
The `routingMode` option is not supported by db-vendo-client. The behavior will be the same as the [`HYBRID` mode of hafas-client](https://github.com/public-transport/hafas-client/blob/main/p/db/readme.md#using-the-routingmode-option), i.e. cancelled trains/infeasible journeys will also be contained for informational purpose.
|
The `routingMode` option is not supported by db-vendo-client. The behavior will be the same as the [`HYBRID` mode of hafas-client](https://github.com/public-transport/hafas-client/blob/main/p/db/readme.md#using-the-routingmode-option), i.e. cancelled trains/infeasible journeys will also be contained for informational purpose.
|
||||||
|
|
@ -10,7 +10,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
{
|
{
|
||||||
results: 8, // maximum number of results
|
results: 8, // maximum number of results
|
||||||
distance: null, // maximum walking distance in meters
|
distance: null, // maximum walking distance in meters
|
||||||
poi: false, // return points of interest?
|
poi: false, // not supported
|
||||||
stops: true, // return stops/stations?
|
stops: true, // return stops/stations?
|
||||||
subStops: true, // not supported
|
subStops: true, // not supported
|
||||||
entrances: true, // not supported
|
entrances: true, // not supported
|
||||||
|
|
|
||||||
|
|
@ -613,7 +613,7 @@ paths:
|
||||||
default: true
|
default: true
|
||||||
- name: linesOfStops
|
- name: linesOfStops
|
||||||
in: query
|
in: query
|
||||||
description: Parse & return lines of each stop/station?
|
description: not supported
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
|
@ -1887,11 +1887,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
|
@ -1995,11 +1995,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
|
@ -2034,15 +2034,15 @@ components:
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
linesOfStops:
|
linesOfStops:
|
||||||
description: parse & expose lines at each stop/station?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
language:
|
language:
|
||||||
|
|
@ -2061,11 +2061,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
|
@ -2088,11 +2088,11 @@ components:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
remarks:
|
remarks:
|
||||||
|
|
@ -2128,11 +2128,11 @@ components:
|
||||||
default: 10
|
default: 10
|
||||||
type: number
|
type: number
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
linesOfStops:
|
linesOfStops:
|
||||||
|
|
@ -2206,7 +2206,7 @@ components:
|
||||||
default: undefined
|
default: undefined
|
||||||
type: number
|
type: number
|
||||||
poi:
|
poi:
|
||||||
description: return points of interest?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
stops:
|
stops:
|
||||||
|
|
@ -2218,15 +2218,15 @@ components:
|
||||||
description: products
|
description: products
|
||||||
default: undefined
|
default: undefined
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
linesOfStops:
|
linesOfStops:
|
||||||
description: parse & expose lines at each stop/station?
|
description: not supported
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
language:
|
language:
|
||||||
|
|
@ -2254,11 +2254,11 @@ components:
|
||||||
description: products
|
description: products
|
||||||
default: undefined
|
default: undefined
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
polylines:
|
polylines:
|
||||||
|
|
@ -2296,11 +2296,11 @@ components:
|
||||||
default: 20
|
default: 20
|
||||||
type: number
|
type: number
|
||||||
subStops:
|
subStops:
|
||||||
description: parse & expose sub-stops of stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
entrances:
|
entrances:
|
||||||
description: parse & expose entrances of stops/stations?
|
description: not supported
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
polylines:
|
polylines:
|
||||||
|
|
|
||||||
32
index.js
32
index.js
|
|
@ -181,6 +181,9 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
scheduledDays: false, // parse & expose dates each journey is valid on?
|
scheduledDays: false, // parse & expose dates each journey is valid on?
|
||||||
notOnlyFastRoutes: false, // if true, also show routes that are mathematically non-optimal
|
notOnlyFastRoutes: false, // if true, also show routes that are mathematically non-optimal
|
||||||
bestprice: false, // search for lowest prices across the entire day
|
bestprice: false, // search for lowest prices across the entire day
|
||||||
|
deutschlandTicketDiscount: false,
|
||||||
|
deutschlandTicketConnectionsOnly: false,
|
||||||
|
bmisNumber: null, // 7-digit BMIS number for business customer rates
|
||||||
}, opt);
|
}, opt);
|
||||||
|
|
||||||
if (opt.when !== undefined) {
|
if (opt.when !== undefined) {
|
||||||
|
|
@ -239,6 +242,9 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // parse & expose entrances of stops/stations?
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
scheduledDays: false, // parse & expose dates the journey is valid on?
|
scheduledDays: false, // parse & expose dates the journey is valid on?
|
||||||
|
deutschlandTicketDiscount: false,
|
||||||
|
deutschlandTicketConnectionsOnly: false,
|
||||||
|
bmisNumber: null, // 7-digit BMIS number for business customer rates
|
||||||
}, opt);
|
}, opt);
|
||||||
|
|
||||||
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken);
|
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken);
|
||||||
|
|
@ -391,7 +397,33 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createBusinessClient = (profile, userAgent, bmisNumber, opt = {}) => {
|
||||||
|
if (!bmisNumber || typeof bmisNumber !== 'string') {
|
||||||
|
throw new TypeError('bmisNumber must be a non-empty string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a client with BMIS number included in all journey requests
|
||||||
|
const client = createClient(profile, userAgent, opt);
|
||||||
|
|
||||||
|
// Wrap journeys method to always include BMIS number
|
||||||
|
const originalJourneys = client.journeys;
|
||||||
|
client.journeys = async (from, to, opt = {}) => {
|
||||||
|
return originalJourneys(from, to, {...opt, bmisNumber});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap refreshJourney method to always include BMIS number
|
||||||
|
if (client.refreshJourney) {
|
||||||
|
const originalRefreshJourney = client.refreshJourney;
|
||||||
|
client.refreshJourney = async (refreshToken, opt = {}) => {
|
||||||
|
return originalRefreshJourney(refreshToken, {...opt, bmisNumber});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createClient,
|
createClient,
|
||||||
|
createBusinessClient,
|
||||||
loadEnrichedStationData,
|
loadEnrichedStationData,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,18 @@ const mapRouteParsers = (route, parsers) => {
|
||||||
default: false,
|
default: false,
|
||||||
parse: parseBoolean,
|
parse: parseBoolean,
|
||||||
},
|
},
|
||||||
|
deutschlandTicketDiscount: {
|
||||||
|
description: 'Calculate ticket prices assuming Deutschlandticket is present',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
deutschlandTicketConnectionsOnly: {
|
||||||
|
description: 'Only return journeys that can be used with the Deutschlandticket',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (route.includes('departures') || route.includes('arrivals')) {
|
if (route.includes('departures') || route.includes('arrivals')) {
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,6 @@ const request = async (ctx, userAgent, reqData) => {
|
||||||
if (reqOptions.query) {
|
if (reqOptions.query) {
|
||||||
url += '?' + stringify(reqOptions.query, {arrayFormat: 'brackets', encodeValuesOnly: true});
|
url += '?' + stringify(reqOptions.query, {arrayFormat: 'brackets', encodeValuesOnly: true});
|
||||||
}
|
}
|
||||||
console.log(url);
|
|
||||||
const reqId = randomBytesHexString(6);
|
const reqId = randomBytesHexString(6);
|
||||||
const fetchReq = new Request(url, reqOptions);
|
const fetchReq = new Request(url, reqOptions);
|
||||||
profile.logRequest(ctx, fetchReq, reqId);
|
profile.logRequest(ctx, fetchReq, reqId);
|
||||||
|
|
@ -122,6 +121,7 @@ const request = async (ctx, userAgent, reqData) => {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// todo [breaking]: make this a FetchError or a HafasClientError?
|
// todo [breaking]: make this a FetchError or a HafasClientError?
|
||||||
|
console.log(JSON.stringify(res), await res.text());
|
||||||
const err = new Error(res.statusText);
|
const err = new Error(res.statusText);
|
||||||
Object.assign(err, errProps);
|
Object.assign(err, errProps);
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
||||||
12
p/db/base.json
Normal file
12
p/db/base.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"journeysEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/fahrplan",
|
||||||
|
"bestpriceEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/tagesbestpreis",
|
||||||
|
"refreshJourneysEndpointTickets": "https://app.vendo.noncd.db.de/mob/angebote/recon",
|
||||||
|
"refreshJourneysEndpointPolyline": "https://app.vendo.noncd.db.de/mob/trip/recon",
|
||||||
|
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",
|
||||||
|
"stopEndpoint": "https://app.vendo.noncd.db.de/mob/location/details/",
|
||||||
|
"nearbyEndpoint": "https://app.vendo.noncd.db.de/mob/location/nearby",
|
||||||
|
"tripEndpoint": "https://app.vendo.noncd.db.de/mob/zuglauf/",
|
||||||
|
"boardEndpoint": "https://app.vendo.noncd.db.de/mob/bahnhofstafel/",
|
||||||
|
"defaultLanguage": "en"
|
||||||
|
}
|
||||||
|
|
@ -1,37 +1,35 @@
|
||||||
import dbnavBase from '../dbnav/base.json' with { type: 'json' };
|
import base from './base.json' with { type: 'json' };
|
||||||
import dbregioguideBase from '../dbregioguide/base.json' with { type: 'json' };
|
|
||||||
import {products} from '../../lib/products.js';
|
import {products} from '../../lib/products.js';
|
||||||
|
|
||||||
// journeys()
|
// journeys()
|
||||||
import {formatJourneysReq} from '../dbnav/journeys-req.js';
|
import {formatJourneysReq} from '../dbnav/journeys-req.js';
|
||||||
const {journeysEndpoint} = dbnavBase;
|
const {journeysEndpoint} = base;
|
||||||
|
|
||||||
// refreshJourneys()
|
// refreshJourneys()
|
||||||
import {formatRefreshJourneyReq} from '../dbnav/journeys-req.js';
|
import {formatRefreshJourneyReq} from '../dbnav/journeys-req.js';
|
||||||
const {refreshJourneysEndpointTickets, refreshJourneysEndpointPolyline} = dbnavBase;
|
const {refreshJourneysEndpointTickets, refreshJourneysEndpointPolyline} = base;
|
||||||
|
|
||||||
// locations()
|
// locations()
|
||||||
import {formatLocationsReq} from '../dbnav/locations-req.js';
|
import {formatLocationsReq} from '../dbnav/locations-req.js';
|
||||||
import {formatLocationFilter} from '../dbnav/location-filter.js';
|
import {formatLocationFilter} from '../dbnav/location-filter.js';
|
||||||
const {locationsEndpoint} = dbnavBase;
|
const {locationsEndpoint} = base;
|
||||||
|
|
||||||
// stop()
|
// stop()
|
||||||
import {formatStopReq} from '../dbnav/stop-req.js';
|
import {formatStopReq} from '../dbnav/stop-req.js';
|
||||||
import {parseStop} from '../dbnav/parse-stop.js';
|
import {parseStop} from '../dbnav/parse-stop.js';
|
||||||
const {stopEndpoint} = dbnavBase;
|
const {stopEndpoint} = base;
|
||||||
|
|
||||||
// nearby()
|
// nearby()
|
||||||
import {formatNearbyReq} from '../dbnav/nearby-req.js';
|
import {formatNearbyReq} from '../dbnav/nearby-req.js';
|
||||||
const {nearbyEndpoint} = dbnavBase;
|
const {nearbyEndpoint} = base;
|
||||||
|
|
||||||
// trip()
|
// trip()
|
||||||
import {formatTripReq} from './trip-req.js';
|
import {formatTripReq} from './trip-req.js';
|
||||||
const tripEndpoint_dbnav = dbnavBase.tripEndpoint;
|
const {tripEndpoint} = base;
|
||||||
const tripEndpoint_dbregioguide = dbregioguideBase.tripEndpoint;
|
|
||||||
|
|
||||||
// arrivals(), departures()
|
// arrivals(), departures()
|
||||||
import {formatStationBoardReq} from '../dbregioguide/station-board-req.js';
|
import {formatStationBoardReq} from '../dbnav/station-board-req.js';
|
||||||
const {boardEndpoint} = dbregioguideBase;
|
const {boardEndpoint} = base;
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
locale: 'de-DE',
|
locale: 'de-DE',
|
||||||
|
|
@ -55,7 +53,7 @@ const profile = {
|
||||||
nearbyEndpoint,
|
nearbyEndpoint,
|
||||||
|
|
||||||
formatTripReq,
|
formatTripReq,
|
||||||
tripEndpoint_dbnav, tripEndpoint_dbregioguide,
|
tripEndpoint,
|
||||||
|
|
||||||
formatStationBoardReq,
|
formatStationBoardReq,
|
||||||
boardEndpoint,
|
boardEndpoint,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
import {formatTripReq as hafasFormatTripReq} from '../dbnav/trip-req.js';
|
import {formatTripReq as hafasFormatTripReq} from '../dbnav/trip-req.js';
|
||||||
import {formatTripReq as risTripReq} from '../dbregioguide/trip-req.js';
|
|
||||||
|
|
||||||
|
|
||||||
const formatTripReq = ({profile, opt}, id) => {
|
const formatTripReq = ({profile, opt}, id) => {
|
||||||
const _profile = {...profile};
|
const _profile = {...profile};
|
||||||
if (id.includes('#')) {
|
_profile['tripEndpoint'] = profile.tripEndpoint;
|
||||||
_profile['tripEndpoint'] = profile.tripEndpoint_dbnav;
|
return hafasFormatTripReq({profile: _profile, opt}, id);
|
||||||
return hafasFormatTripReq({profile: _profile, opt}, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
_profile['tripEndpoint'] = profile.tripEndpoint_dbregioguide;
|
|
||||||
return risTripReq({profile: _profile, opt}, id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"journeysEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/fahrplan",
|
"journeysEndpoint": "https://app.services-bahn.de/mob/angebote/fahrplan",
|
||||||
"bestpriceEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/tagesbestpreis",
|
"bestpriceEndpoint": "https://app.services-bahn.de/mob/angebote/tagesbestpreis",
|
||||||
"refreshJourneysEndpointTickets": "https://app.vendo.noncd.db.de/mob/angebote/recon",
|
"refreshJourneysEndpointTickets": "https://app.services-bahn.de/mob/angebote/recon",
|
||||||
"refreshJourneysEndpointPolyline": "https://app.vendo.noncd.db.de/mob/trip/recon",
|
"refreshJourneysEndpointPolyline": "https://app.services-bahn.de/mob/trip/recon",
|
||||||
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",
|
"locationsEndpoint": "https://app.services-bahn.de/mob/location/search",
|
||||||
"stopEndpoint": "https://app.vendo.noncd.db.de/mob/location/details/",
|
"stopEndpoint": "https://app.services-bahn.de/mob/location/details/",
|
||||||
"nearbyEndpoint": "https://app.vendo.noncd.db.de/mob/location/nearby",
|
"nearbyEndpoint": "https://app.services-bahn.de/mob/location/nearby",
|
||||||
"tripEndpoint": "https://app.vendo.noncd.db.de/mob/zuglauf/",
|
"tripEndpoint": "https://app.services-bahn.de/mob/zuglauf/",
|
||||||
"boardEndpoint": "https://app.vendo.noncd.db.de/mob/bahnhofstafel/",
|
"boardEndpoint": "https://app.services-bahn.de/mob/bahnhofstafel/",
|
||||||
"defaultLanguage": "en"
|
"defaultLanguage": "en"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,15 @@ const formatBaseJourneysReq = (ctx) => {
|
||||||
// TODO opt.accessibility
|
// TODO opt.accessibility
|
||||||
// TODO routingMode
|
// TODO routingMode
|
||||||
const travellers = ctx.profile.formatTravellers(ctx);
|
const travellers = ctx.profile.formatTravellers(ctx);
|
||||||
return {
|
const baseReq = {
|
||||||
autonomeReservierung: false,
|
autonomeReservierung: false,
|
||||||
einstiegsTypList: [
|
einstiegsTypList: [
|
||||||
'STANDARD',
|
'STANDARD',
|
||||||
],
|
],
|
||||||
|
fahrverguenstigungen: {
|
||||||
|
deutschlandTicketVorhanden: ctx.opt.deutschlandTicketDiscount,
|
||||||
|
nurDeutschlandTicketVerbindungen: ctx.opt.deutschlandTicketConnectionsOnly,
|
||||||
|
},
|
||||||
klasse: travellers.klasse,
|
klasse: travellers.klasse,
|
||||||
reisendenProfil: {
|
reisendenProfil: {
|
||||||
reisende: travellers.reisende.map(t => {
|
reisende: travellers.reisende.map(t => {
|
||||||
|
|
@ -23,6 +27,16 @@ const formatBaseJourneysReq = (ctx) => {
|
||||||
},
|
},
|
||||||
reservierungsKontingenteVorhanden: false,
|
reservierungsKontingenteVorhanden: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add business customer affiliation if BMIS number is provided
|
||||||
|
if (ctx.opt.bmisNumber) {
|
||||||
|
baseReq.firmenZugehoerigkeit = {
|
||||||
|
bmisNr: ctx.opt.bmisNumber,
|
||||||
|
identifikationsart: 'BMIS',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseReq;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
|
|
@ -39,6 +53,7 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
wunsch: {
|
wunsch: {
|
||||||
abgangsLocationId: from.lid,
|
abgangsLocationId: from.lid,
|
||||||
verkehrsmittel: filters,
|
verkehrsmittel: filters,
|
||||||
|
alternativeHalteBerechnung: true, // what is this?
|
||||||
zeitWunsch: {
|
zeitWunsch: {
|
||||||
reiseDatum: profile.formatTime(profile, when, true),
|
reiseDatum: profile.formatTime(profile, when, true),
|
||||||
zeitPunktArt: outFrwd ? 'ABFAHRT' : 'ANKUNFT',
|
zeitPunktArt: outFrwd ? 'ABFAHRT' : 'ANKUNFT',
|
||||||
|
|
@ -61,7 +76,7 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
return {
|
return {
|
||||||
endpoint: opt.bestprice ? profile.bestpriceEndpoint : profile.journeysEndpoint,
|
endpoint: opt.bestprice ? profile.bestpriceEndpoint : profile.journeysEndpoint,
|
||||||
body: query,
|
body: query,
|
||||||
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v8+json'),
|
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v9+json'),
|
||||||
method: 'post',
|
method: 'post',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -78,7 +93,7 @@ const formatRefreshJourneyReq = (ctx, refreshToken) => {
|
||||||
return {
|
return {
|
||||||
endpoint: opt.tickets ? profile.refreshJourneysEndpointTickets : profile.refreshJourneysEndpointPolyline,
|
endpoint: opt.tickets ? profile.refreshJourneysEndpointTickets : profile.refreshJourneysEndpointPolyline,
|
||||||
body: query,
|
body: query,
|
||||||
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v8+json'),
|
headers: getHeaders('application/x.db.vendo.mob.verbindungssuche.v9+json'),
|
||||||
method: 'post',
|
method: 'post',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const profile = {
|
||||||
journeysOutFrwd: false,
|
journeysOutFrwd: false,
|
||||||
departuresGetPasslist: false,
|
departuresGetPasslist: false,
|
||||||
departuresStbFltrEquiv: true,
|
departuresStbFltrEquiv: true,
|
||||||
trip: false,
|
trip: true,
|
||||||
radar: false,
|
radar: false,
|
||||||
refreshJourney: false,
|
refreshJourney: false,
|
||||||
journeysFromTrip: false,
|
journeysFromTrip: false,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
let query = {
|
let query = {
|
||||||
maxUmstiege: transfers,
|
maxUmstiege: transfers,
|
||||||
minUmstiegszeit: opt.transferTime,
|
minUmstiegszeit: opt.transferTime,
|
||||||
deutschlandTicketVorhanden: false,
|
deutschlandTicketVorhanden: opt.deutschlandTicketDiscount,
|
||||||
nurDeutschlandTicketVerbindungen: false,
|
nurDeutschlandTicketVerbindungen: opt.deutschlandTicketConnectionsOnly,
|
||||||
reservierungsKontingenteVorhanden: false,
|
reservierungsKontingenteVorhanden: false,
|
||||||
schnelleVerbindungen: !opt.notOnlyFastRoutes,
|
schnelleVerbindungen: !opt.notOnlyFastRoutes,
|
||||||
sitzplatzOnly: false,
|
sitzplatzOnly: false,
|
||||||
|
|
|
||||||
2900
package-lock.json
generated
2900
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "db-vendo-client",
|
"name": "db-vendo-client",
|
||||||
"description": "Client for bahn.de public transport APIs.",
|
"description": "Client for bahn.de public transport APIs.",
|
||||||
"version": "6.8.0",
|
"version": "6.10.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
"test-e2e": "VCR_OFF=true tap -t60 -j16 test/e2e/*.js",
|
"test-e2e": "VCR_OFF=true tap -t60 -j16 test/e2e/*.js",
|
||||||
"test-spelling": "cspell .",
|
"test-spelling": "cspell .",
|
||||||
"test": "npm run test-unit && npm run test-integration && npm run test-spelling",
|
"test": "npm run test-unit && npm run test-integration && npm run test-spelling",
|
||||||
"prepublishOnly": "npm run lint && npm test",
|
"prepublishOnly": "npm run lint",
|
||||||
"api": "node api.js"
|
"api": "node api.js"
|
||||||
},
|
},
|
||||||
"tap": {
|
"tap": {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ const parseTickets = (ctx, j) => {
|
||||||
.flatMap(p => [
|
.flatMap(p => [
|
||||||
p.einfacheFahrt?.standard?.reisePosition,
|
p.einfacheFahrt?.standard?.reisePosition,
|
||||||
p.einfacheFahrt?.upsellEntgelt?.einfacheFahrt?.reisePosition,
|
p.einfacheFahrt?.upsellEntgelt?.einfacheFahrt?.reisePosition,
|
||||||
].filter(p => p)
|
p.einfacheFahrt?.upsellAngebote?.map(a => a.upsellEntgelt?.einfacheFahrt?.reisePosition),
|
||||||
|
].flatMap(p => p)
|
||||||
|
.filter(p => p)
|
||||||
.map(p => {
|
.map(p => {
|
||||||
p.reisePosition.teilpreis = Boolean(p.teilpreisInformationen?.length);
|
p.reisePosition.teilpreis = Boolean(p.teilpreisInformationen?.length);
|
||||||
return p.reisePosition;
|
return p.reisePosition;
|
||||||
|
|
@ -44,7 +46,7 @@ const parseTickets = (ctx, j) => {
|
||||||
amount: Math.round(s.preis?.betrag * 100),
|
amount: Math.round(s.preis?.betrag * 100),
|
||||||
currency: s.preis?.waehrung,
|
currency: s.preis?.waehrung,
|
||||||
},
|
},
|
||||||
firstClass: s.klasse == 'KLASSE_1' || s.premium || Boolean(s.nutzungsInformationen?.find(i => i.klasse == 'KLASSE_1')),
|
firstClass: s.klasse == 'KLASSE_1' || Boolean(s.nutzungsInformationen?.find(i => i.klasse == 'KLASSE_1')),
|
||||||
partialFare: s.teilpreis,
|
partialFare: s.teilpreis,
|
||||||
};
|
};
|
||||||
if (s.teilpreis) {
|
if (s.teilpreis) {
|
||||||
|
|
|
||||||
31
readme.md
31
readme.md
|
|
@ -23,25 +23,25 @@ What doesn't work:
|
||||||
|
|
||||||
Depending on the configured profile, db-vendo-client will use multiple different DB APIs that offer varying functionality, so choose wisely:
|
Depending on the configured profile, db-vendo-client will use multiple different DB APIs that offer varying functionality, so choose wisely:
|
||||||
|
|
||||||
| Profile | `db` | `dbnav` | `dbweb` | `dbbahnhof` | `dbris` |
|
| Profile | `db` | `dbnav` | `dbweb` | `dbris` |
|
||||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||||
| no API key required | ✅ | ✅ | ✅ | ✅ | ❌ |
|
| no API key required | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||||
| all above endpoints supported | ✅ | ✅ | except `stop()` | only boards | only boards |
|
| all above endpoints supported | ✅ | ✅ | except `stop()` | only boards |
|
||||||
| duration for boards | up to 12h | always 1h | always 1h | up to 6h, only from current time | up to 12h |
|
| duration for boards | always 1h | always 1h | always 1h | up to 12h |
|
||||||
| remarks | not for boards | for boards only most important remarks | all remarks on boards and journeys | most remarks | all remarks |
|
| remarks | for boards only most important remarks | for boards only most important remarks | all remarks on boards and journeys | all remarks |
|
||||||
| cancelled trips | contained with cancelled flag in journeys, not contained in boards | contained with cancelled flag | contained with cancelled flag | contained with cancelled flag | contained with cancelled flag |
|
| cancelled trips | contained with cancelled flag | contained with cancelled flag | contained with cancelled flag | contained with cancelled flag |
|
||||||
| tickets | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | ❌ | ❌ |
|
| tickets | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | only for `refreshJourney()`, mutually exclusive with polylines | ❌ |
|
||||||
| polylines | only for `refreshJourney()` (mutually exclusive with tickets) and for `trip()` (only for HAFAS trip ids) | only for `refreshJourney()/trip()`, mutually exclusive with tickets | only for `refreshJourney()/trip()`, mutually exclusive with tickets | ❌ | ❌ |
|
| polylines | only for `refreshJourney()/trip()`, mutually exclusive with tickets | only for `refreshJourney()/trip()`, mutually exclusive with tickets | only for `refreshJourney()/trip()`, mutually exclusive with tickets | ❌ |
|
||||||
| trip ids used | HAFAS trip ids for journeys, RIS trip ids for boards (static on train splits?) | HAFAS trip ids | HAFAS trip ids | RIS trip ids | RIS trip ids |
|
| trip ids used | HAFAS trip ids | HAFAS trip ids | HAFAS trip ids | RIS trip ids |
|
||||||
| line.id/fahrtNr used | actual fahrtNr | actual fahrtNr for journeys, unreliable/route id for boards and `trip()` | unreliable/route id | unreliable/route id | ✅ |
|
| line.id/fahrtNr used | actual fahrtNr for journeys, unreliable/route id for boards and `trip()` | actual fahrtNr for journeys, unreliable/route id for boards and `trip()` | unreliable/route id | ✅ |
|
||||||
| adminCode/operator | ✅ | only for journeys | only operator | only adminCode | ✅ |
|
| adminCode/operator | only for journeys | only for journeys | only operator | ✅ |
|
||||||
| stopovers | not in boards | not in boards | ✅ | some | ✅ |
|
| stopovers | not in boards | not in boards | ✅ | ✅ |
|
||||||
| assumed backend API stability | less stable | more stable | less stable | less stable | more stable |
|
| assumed backend API stability | less stable | more stable | less stable | more stable |
|
||||||
| quotas | 60 requests per minute for journeys, unknown for boards (IPv4) | 60 requests per minute (IPv4) | aggressive blocking (IPv4/IPv6) | ? | depends on API key |
|
| quotas | 60 requests per minute (IPv4) | 60 requests per minute (IPv4), possibly aggressive blocking | aggressive blocking (IPv4/IPv6) | depends on API key |
|
||||||
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> If you think that for your project, quotas may become an issue, [consider alternative ways to obtain the data you need.](https://github.com/derhuerst/db-rest/blob/6/docs/readme.md#why-not-to-use-this-api).
|
> If you think that for your project, quotas may become an issue, [consider alternative ways to obtain the data you need.](https://github.com/derhuerst/db-rest/blob/6/docs/readme.md#why-not-to-use-this-api), e.g. [motis-fptf-client](https://github.com/motis-project/motis-fptf-client) (a drop-in replacement for db-vendo-client/hafas-client) in conjunction with https://transitous.org (please consider the [usage policy](https://transitous.org/api/) there) or a self-hosted [MOTIS](https://github.com/motis-project/motis) instance.
|
||||||
|
|
||||||
Feel free to report anything that you stumble upon via Issues or create a PR :)
|
Feel free to report anything that you stumble upon via Issues or create a PR :)
|
||||||
|
|
||||||
|
|
@ -92,6 +92,7 @@ There are [community-maintained TypeScript typings available as `@types/hafas-cl
|
||||||
- [hafas-client](https://github.com/public-transport/hafas-client/) – including further related projects
|
- [hafas-client](https://github.com/public-transport/hafas-client/) – including further related projects
|
||||||
- [hafas-rest-api](https://github.com/public-transport/hafas-rest-api/) – expose a hafas-client or db-vendo-client instance as a REST API
|
- [hafas-rest-api](https://github.com/public-transport/hafas-rest-api/) – expose a hafas-client or db-vendo-client instance as a REST API
|
||||||
- [db-rest](https://github.com/derhuerst/db-rest/) – for the legacy DB HAFAS endpoint
|
- [db-rest](https://github.com/derhuerst/db-rest/) – for the legacy DB HAFAS endpoint
|
||||||
|
- [motis-fptf-client](https://github.com/motis-project/motis-fptf-client) – a drop-in replacement for db-vendo-client/hafas-client wrapping a [MOTIS](https://github.com/motis-project/motis) instance
|
||||||
- [`*.transport.rest`](https://transport.rest/) – Public APIs wrapping some HAFAS endpoints.
|
- [`*.transport.rest`](https://transport.rest/) – Public APIs wrapping some HAFAS endpoints.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {testJourneysWithDetour} from './lib/journeys-with-detour.js';
|
||||||
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
||||||
const minute = 60 * 1000;
|
const minute = 60 * 1000;
|
||||||
|
|
||||||
const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00
|
const T_MOCK = 1764831628 * 1000; // Thu Dec 04 2025 07:00:28 GMT+0000
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|
@ -127,7 +127,7 @@ if (!process.env.VCR_OFF) {
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('refreshJourney – valid tickets', async (t) => {
|
tap.test('refreshJourney – valid tickets', async (t) => {
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
const T_MOCK = 1758279600 * 1000;
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
||||||
|
|
@ -261,7 +261,7 @@ if (!process.env.VCR_MODE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tap.test('refreshJourney', async (t) => {
|
tap.test('refreshJourney', async (t) => {
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
const T_MOCK = 1763542800 * 1000;
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
const validate = createValidate({...cfg, when});
|
const validate = createValidate({...cfg, when});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import isRoughlyEqual from 'is-roughly-equal';
|
||||||
|
|
||||||
import {createWhen} from './lib/util.js';
|
import {createWhen} from './lib/util.js';
|
||||||
import {createClient} from '../../index.js';
|
import {createClient} from '../../index.js';
|
||||||
import {profile as dbProfile} from '../../p/dbregioguide/index.js';
|
import {profile as dbProfile} from '../../p/dbbahnhof/index.js';
|
||||||
import {
|
import {
|
||||||
createValidateStation,
|
createValidateStation,
|
||||||
createValidateTrip,
|
createValidateTrip,
|
||||||
|
|
@ -23,7 +23,7 @@ import {testJourneysWithDetour} from './lib/journeys-with-detour.js';
|
||||||
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
||||||
const minute = 60 * 1000;
|
const minute = 60 * 1000;
|
||||||
|
|
||||||
const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00
|
const T_MOCK = 1764831628 * 1000; // Thu Dec 04 2025 07:00:28 GMT+0000
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|
@ -100,7 +100,7 @@ const berlinSüdkreuz = '8011113';
|
||||||
const kölnHbf = '8000207';
|
const kölnHbf = '8000207';
|
||||||
|
|
||||||
|
|
||||||
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
tap.skip('departures at Berlin Schwedter Str.', async (t) => {
|
||||||
const res = await client.departures(blnSchwedterStr, {
|
const res = await client.departures(blnSchwedterStr, {
|
||||||
duration: 5, when,
|
duration: 5, when,
|
||||||
});
|
});
|
||||||
|
|
@ -114,7 +114,7 @@ tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('departures with station object', async (t) => {
|
tap.skip('departures with station object', async (t) => {
|
||||||
const res = await client.departures({
|
const res = await client.departures({
|
||||||
type: 'station',
|
type: 'station',
|
||||||
id: jungfernheide,
|
id: jungfernheide,
|
||||||
|
|
@ -130,7 +130,7 @@ tap.test('departures with station object', async (t) => {
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
tap.skip('arrivals at Berlin Schwedter Str.', async (t) => {
|
||||||
const res = await client.arrivals(blnSchwedterStr, {
|
const res = await client.arrivals(blnSchwedterStr, {
|
||||||
duration: 5, when,
|
duration: 5, when,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {testJourneysWithDetour} from './lib/journeys-with-detour.js';
|
||||||
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
||||||
const minute = 60 * 1000;
|
const minute = 60 * 1000;
|
||||||
|
|
||||||
const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00
|
const T_MOCK = 1764831628 * 1000; // Thu Dec 04 2025 07:00:28 GMT+0000
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|
@ -126,7 +126,7 @@ tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('refreshJourney – valid tickets', async (t) => {
|
tap.test('refreshJourney – valid tickets', async (t) => {
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
const T_MOCK = 1758279600 * 1000;
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
|
|
||||||
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
||||||
|
|
@ -261,7 +261,7 @@ if (!process.env.VCR_OFF) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tap.test('refreshJourney', async (t) => {
|
tap.test('refreshJourney', async (t) => {
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
const T_MOCK = 1763542800 * 1000;
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
||||||
const validate = createValidate({...cfg, when});
|
const validate = createValidate({...cfg, when});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,499 +0,0 @@
|
||||||
import tap from 'tap';
|
|
||||||
import isRoughlyEqual from 'is-roughly-equal';
|
|
||||||
|
|
||||||
import {createWhen} from './lib/util.js';
|
|
||||||
import {createClient} from '../../index.js';
|
|
||||||
import {profile as dbProfile} from '../../p/dbregioguide/index.js';
|
|
||||||
import {
|
|
||||||
createValidateStation,
|
|
||||||
createValidateTrip,
|
|
||||||
} from './lib/validators.js';
|
|
||||||
import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js';
|
|
||||||
import {testJourneysStationToStation} from './lib/journeys-station-to-station.js';
|
|
||||||
import {testJourneysStationToAddress} from './lib/journeys-station-to-address.js';
|
|
||||||
import {testJourneysStationToPoi} from './lib/journeys-station-to-poi.js';
|
|
||||||
import {testEarlierLaterJourneys} from './lib/earlier-later-journeys.js';
|
|
||||||
import {testLegCycleAlternatives} from './lib/leg-cycle-alternatives.js';
|
|
||||||
import {testRefreshJourney} from './lib/refresh-journey.js';
|
|
||||||
import {journeysFailsWithNoProduct} from './lib/journeys-fails-with-no-product.js';
|
|
||||||
import {testDepartures} from './lib/departures.js';
|
|
||||||
import {testArrivals} from './lib/arrivals.js';
|
|
||||||
import {testJourneysWithDetour} from './lib/journeys-with-detour.js';
|
|
||||||
|
|
||||||
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o);
|
|
||||||
const minute = 60 * 1000;
|
|
||||||
|
|
||||||
const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
|
|
||||||
const cfg = {
|
|
||||||
when,
|
|
||||||
stationCoordsOptional: true, // TODO
|
|
||||||
products: dbProfile.products,
|
|
||||||
minLatitude: 46.673100,
|
|
||||||
maxLatitude: 55.030671,
|
|
||||||
minLongitude: 6.896517,
|
|
||||||
maxLongitude: 16.180237,
|
|
||||||
};
|
|
||||||
|
|
||||||
const validate = createValidate(cfg);
|
|
||||||
|
|
||||||
const assertValidPrice = (t, p) => {
|
|
||||||
t.ok(p);
|
|
||||||
if (p.amount !== null) {
|
|
||||||
t.equal(typeof p.amount, 'number');
|
|
||||||
t.ok(p.amount > 0);
|
|
||||||
}
|
|
||||||
if (p.hint !== null) {
|
|
||||||
t.equal(typeof p.hint, 'string');
|
|
||||||
t.ok(p.hint);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const assertValidTickets = (test, tickets) => {
|
|
||||||
test.ok(Array.isArray(tickets));
|
|
||||||
for (let fare of tickets) {
|
|
||||||
test.equal(typeof fare.name, 'string', 'Mandatory field "name" is missing or not a string');
|
|
||||||
test.ok(fare.name);
|
|
||||||
|
|
||||||
test.ok(isObj(fare.priceObj), 'Mandatory field "priceObj" is missing or not an object');
|
|
||||||
test.equal(typeof fare.priceObj.amount, 'number', 'Mandatory field "amount" in "priceObj" is missing or not a number');
|
|
||||||
test.ok(fare.priceObj.amount > 0);
|
|
||||||
if ('currency' in fare.priceObj) {
|
|
||||||
test.equal(typeof fare.priceObj.currency, 'string');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check optional fields
|
|
||||||
if ('addData' in fare) {
|
|
||||||
test.equal(typeof fare.addData, 'string');
|
|
||||||
}
|
|
||||||
if ('addDataTicketInfo' in fare) {
|
|
||||||
test.equal(typeof fare.addDataTicketInfo, 'string');
|
|
||||||
}
|
|
||||||
if ('addDataTicketDetails' in fare) {
|
|
||||||
test.equal(typeof fare.addDataTicketDetails, 'string');
|
|
||||||
}
|
|
||||||
if ('addDataTravelInfo' in fare) {
|
|
||||||
test.equal(typeof fare.addDataTravelInfo, 'string');
|
|
||||||
}
|
|
||||||
if ('addDataTravelDetails' in fare) {
|
|
||||||
test.equal(typeof fare.firstClass, 'boolean');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const client = createClient(dbProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
|
||||||
|
|
||||||
const berlinHbf = '8011160';
|
|
||||||
const münchenHbf = '8000261';
|
|
||||||
const jungfernheide = '8011167';
|
|
||||||
const blnSchwedterStr = '732652';
|
|
||||||
const westhafen = '8089116';
|
|
||||||
const wedding = '8089131';
|
|
||||||
const württembergallee = '731084';
|
|
||||||
const regensburgHbf = '8000309';
|
|
||||||
const blnOstbahnhof = '8010255';
|
|
||||||
const blnTiergarten = '8089091';
|
|
||||||
const blnJannowitzbrücke = '8089019';
|
|
||||||
const potsdamHbf = '8012666';
|
|
||||||
const berlinSüdkreuz = '8011113';
|
|
||||||
const kölnHbf = '8000207';
|
|
||||||
|
|
||||||
/*
|
|
||||||
tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => {
|
|
||||||
const res = await client.journeys(blnSchwedterStr, münchenHbf, {
|
|
||||||
results: 4,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysStationToStation({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
toId: münchenHbf,
|
|
||||||
});
|
|
||||||
// todo: find a journey where there pricing info is always available
|
|
||||||
for (let journey of res.journeys) {
|
|
||||||
if (journey.price) {
|
|
||||||
assertValidPrice(t, journey.price);
|
|
||||||
}
|
|
||||||
if (journey.tickets) {
|
|
||||||
assertValidTickets(t, journey.tickets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('refreshJourney – valid tickets', async (t) => {
|
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
|
|
||||||
const journeysRes = await client.journeys(berlinHbf, münchenHbf, {
|
|
||||||
results: 4,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
|
||||||
tickets: false,
|
|
||||||
});
|
|
||||||
const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, {
|
|
||||||
tickets: true,
|
|
||||||
});
|
|
||||||
for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) {
|
|
||||||
if (res.journey.tickets !== undefined) {
|
|
||||||
assertValidTickets(t, res.journey.tickets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: journeys, only one product
|
|
||||||
|
|
||||||
tap.test('journeys – fails with no product', async (t) => {
|
|
||||||
await journeysFailsWithNoProduct({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
products: dbProfile.products,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => {
|
|
||||||
const torfstr = {
|
|
||||||
type: 'location',
|
|
||||||
address: 'Torfstraße 17',
|
|
||||||
latitude: 52.5416823,
|
|
||||||
longitude: 13.3491223,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, torfstr, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysStationToAddress({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: torfstr,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => {
|
|
||||||
const atze = {
|
|
||||||
type: 'location',
|
|
||||||
id: '991598902',
|
|
||||||
poi: true,
|
|
||||||
name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U',
|
|
||||||
latitude: 52.542417,
|
|
||||||
longitude: 13.350437,
|
|
||||||
};
|
|
||||||
const res = await client.journeys(blnSchwedterStr, atze, {
|
|
||||||
results: 3,
|
|
||||||
departure: when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysStationToPoi({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
fromId: blnSchwedterStr,
|
|
||||||
to: atze,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('journeys: via works – with detour', async (t) => {
|
|
||||||
// Going from Westhafen to Wedding via Württembergallee without detour
|
|
||||||
// is currently impossible. We check if the routing engine computes a detour.
|
|
||||||
const res = await client.journeys(westhafen, wedding, {
|
|
||||||
via: württembergallee,
|
|
||||||
results: 1,
|
|
||||||
departure: when,
|
|
||||||
stopovers: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testJourneysWithDetour({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
detourIds: [württembergallee],
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide
|
|
||||||
// todo: without detour
|
|
||||||
|
|
||||||
|
|
||||||
// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future
|
|
||||||
tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => {
|
|
||||||
await testEarlierLaterJourneys({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
validate,
|
|
||||||
fromId: jungfernheide,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!process.env.VCR_MODE) {
|
|
||||||
tap.test('journeys – leg cycle & alternatives', async (t) => {
|
|
||||||
await testLegCycleAlternatives({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
fromId: blnTiergarten,
|
|
||||||
toId: blnJannowitzbrücke,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tap.test('refreshJourney', async (t) => {
|
|
||||||
const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00
|
|
||||||
const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK);
|
|
||||||
const validate = createValidate({...cfg, when});
|
|
||||||
|
|
||||||
await testRefreshJourney({
|
|
||||||
test: t,
|
|
||||||
fetchJourneys: client.journeys,
|
|
||||||
refreshJourney: client.refreshJourney,
|
|
||||||
validate,
|
|
||||||
fromId: jungfernheide,
|
|
||||||
toId: münchenHbf,
|
|
||||||
when,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => {
|
|
||||||
const blnMehringdamm = '730939';
|
|
||||||
const blnStadtmitte = '732541';
|
|
||||||
const blnNaturkundemuseum = '732539';
|
|
||||||
const blnSpittelmarkt = '732543';
|
|
||||||
|
|
||||||
const isU6Leg = leg => leg.line && leg.line.name
|
|
||||||
&& leg.line.name.toUpperCase()
|
|
||||||
.replace(/\s+/g, '') === 'U6';
|
|
||||||
|
|
||||||
const sameStopOrStation = (stopA) => (stopB) => {
|
|
||||||
if (stopA.id && stopB.id && stopA.id === stopB.id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const statA = stopA.stat && stopA.stat.id || NaN;
|
|
||||||
const statB = stopB.stat && stopB.stat.id || NaN;
|
|
||||||
return statA === statB || stopA.id === statB || stopB.id === statA;
|
|
||||||
};
|
|
||||||
const departureOf = st => Number(new Date(st.departure || st.scheduledDeparture));
|
|
||||||
const arrivalOf = st => Number(new Date(st.arrival || st.scheduledArrival));
|
|
||||||
|
|
||||||
// `journeysFromTrip` only supports queries *right now*, so we can't use `when` as in all
|
|
||||||
// other tests. To make the test less brittle, we pick a connection that is served all night. 🙄
|
|
||||||
const when = new Date();
|
|
||||||
const validate = createValidate({...cfg, when});
|
|
||||||
|
|
||||||
const findTripBetween = async (stopAId, stopBId, products = {}) => {
|
|
||||||
const {journeys} = await client.journeys(stopAId, stopBId, {
|
|
||||||
departure: new Date(when - 10 * minute),
|
|
||||||
transfers: 0, products,
|
|
||||||
results: 8, stopovers: false, remarks: false,
|
|
||||||
});
|
|
||||||
for (const j of journeys) {
|
|
||||||
const l = j.legs.find(isU6Leg);
|
|
||||||
if (!l) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const t = await client.trip(l.tripId, {
|
|
||||||
stopovers: true, remarks: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pastStopovers = t.stopovers
|
|
||||||
.filter(st => departureOf(st) < Date.now()); // todo: <= ?
|
|
||||||
const hasStoppedAtA = pastStopovers
|
|
||||||
.find(sameStopOrStation({id: stopAId}));
|
|
||||||
const willStopAtB = t.stopovers
|
|
||||||
.filter(st => arrivalOf(st) > Date.now()) // todo: >= ?
|
|
||||||
.find(sameStopOrStation({id: stopBId}));
|
|
||||||
|
|
||||||
if (hasStoppedAtA && willStopAtB) {
|
|
||||||
const prevStopover = maxBy(pastStopovers, departureOf);
|
|
||||||
return {trip: t, prevStopover};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {trip: null, prevStopover: null};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Find a vehicle from U Mehringdamm to U Stadtmitte (to the north) that is currently
|
|
||||||
// between these two stations.
|
|
||||||
const {trip, prevStopover} = await findTripBetween(blnMehringdamm, blnStadtmitte, {
|
|
||||||
regionalExpress: false, regional: false, suburban: false,
|
|
||||||
});
|
|
||||||
t.ok(trip, 'precondition failed: trip not found');
|
|
||||||
t.ok(prevStopover, 'precondition failed: previous stopover missing');
|
|
||||||
|
|
||||||
// todo: "Error: Suche aus dem Zug: Vor Abfahrt des Zuges"
|
|
||||||
const newJourneys = await client.journeysFromTrip(trip.id, prevStopover, blnSpittelmarkt, {
|
|
||||||
results: 3, stopovers: true, remarks: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate with fake prices.
|
|
||||||
const withFakePrice = (j) => {
|
|
||||||
const clone = Object.assign({}, j);
|
|
||||||
clone.price = {amount: 123, currency: 'EUR'};
|
|
||||||
return clone;
|
|
||||||
};
|
|
||||||
// todo: there is no such validator!
|
|
||||||
validate(t, newJourneys.map(withFakePrice), 'journeysFromTrip', 'newJourneys');
|
|
||||||
|
|
||||||
for (let i = 0; i < newJourneys.length; i++) {
|
|
||||||
const j = newJourneys[i];
|
|
||||||
const n = `newJourneys[${i}]`;
|
|
||||||
|
|
||||||
const legOnTrip = j.legs.find(l => l.tripId === trip.id);
|
|
||||||
t.ok(legOnTrip, n + ': leg with trip ID not found');
|
|
||||||
t.equal(last(legOnTrip.stopovers).stop.id, blnStadtmitte);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('trip details', async (t) => {
|
|
||||||
const res = await client.journeys(berlinHbf, münchenHbf, {
|
|
||||||
results: 1, departure: when,
|
|
||||||
});
|
|
||||||
|
|
||||||
const p = res.journeys[0].legs.find(l => !l.walking);
|
|
||||||
t.ok(p.tripId, 'precondition failed');
|
|
||||||
t.ok(p.line.name, 'precondition failed');
|
|
||||||
|
|
||||||
const tripRes = await client.trip(p.tripId, {when});
|
|
||||||
|
|
||||||
const validate = createValidate(cfg, {
|
|
||||||
trip: (cfg) => {
|
|
||||||
const validateTrip = createValidateTrip(cfg);
|
|
||||||
const validateTripWithFakeDirection = (val, trip, name) => {
|
|
||||||
validateTrip(val, {
|
|
||||||
...trip,
|
|
||||||
direction: trip.direction || 'foo', // todo, see #49
|
|
||||||
}, name);
|
|
||||||
};
|
|
||||||
return validateTripWithFakeDirection;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
validate(t, tripRes, 'tripResult', 'tripRes');
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
|
||||||
const res = await client.departures(blnSchwedterStr, {
|
|
||||||
duration: 5, when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testDepartures({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
id: blnSchwedterStr,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('departures with station object', async (t) => {
|
|
||||||
const res = await client.departures({
|
|
||||||
type: 'station',
|
|
||||||
id: jungfernheide,
|
|
||||||
name: 'Berlin Jungfernheide',
|
|
||||||
location: {
|
|
||||||
type: 'location',
|
|
||||||
latitude: 1.23,
|
|
||||||
longitude: 2.34,
|
|
||||||
},
|
|
||||||
}, {when});
|
|
||||||
|
|
||||||
validate(t, res, 'departuresResponse', 'res');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
|
||||||
const res = await client.arrivals(blnSchwedterStr, {
|
|
||||||
duration: 5, when,
|
|
||||||
});
|
|
||||||
|
|
||||||
await testArrivals({
|
|
||||||
test: t,
|
|
||||||
res,
|
|
||||||
validate,
|
|
||||||
id: blnSchwedterStr,
|
|
||||||
});
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
tap.test('nearby Berlin Jungfernheide', async (t) => {
|
|
||||||
const nearby = await client.nearby({
|
|
||||||
type: 'location',
|
|
||||||
latitude: 52.530273,
|
|
||||||
longitude: 13.299433,
|
|
||||||
}, {
|
|
||||||
results: 2, distance: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
validate(t, nearby, 'locations', 'nearby');
|
|
||||||
|
|
||||||
t.equal(nearby.length, 2);
|
|
||||||
|
|
||||||
const s0 = nearby[0];
|
|
||||||
t.equal(s0.id, jungfernheide);
|
|
||||||
t.equal(s0.name, 'Berlin Jungfernheide');
|
|
||||||
t.ok(isRoughlyEqual(0.0005, s0.location.latitude, 52.530408));
|
|
||||||
t.ok(isRoughlyEqual(0.0005, s0.location.longitude, 13.299424));
|
|
||||||
t.ok(s0.distance >= 0);
|
|
||||||
t.ok(s0.distance <= 100);
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('locations named Jungfernheide', async (t) => {
|
|
||||||
const locations = await client.locations('Jungfernheide', {
|
|
||||||
results: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
validate(t, locations, 'locations', 'locations');
|
|
||||||
t.ok(locations.length <= 10);
|
|
||||||
t.ok(locations.some((l) => {
|
|
||||||
return l.station && l.station.id === jungfernheide || l.id === jungfernheide;
|
|
||||||
}), 'Jungfernheide not found');
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('stop', async (t) => {
|
|
||||||
const s = await client.stop(regensburgHbf);
|
|
||||||
|
|
||||||
validate(t, s, ['stop', 'station'], 'stop');
|
|
||||||
t.equal(s.id, regensburgHbf);
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('line with additionalName', async (t) => {
|
|
||||||
const {departures} = await client.departures(potsdamHbf, {
|
|
||||||
when,
|
|
||||||
duration: 12 * 60, // 12 minutes
|
|
||||||
products: {bus: false, suburban: false, tram: false},
|
|
||||||
});
|
|
||||||
t.ok(departures.some(d => d.line && d.line.additionalName));
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -3,7 +3,7 @@ const simplify = j => j.legs.map(l => {
|
||||||
origin: l.origin,
|
origin: l.origin,
|
||||||
destination: l.destination,
|
destination: l.destination,
|
||||||
departure: l.plannedDeparture || l.departure,
|
departure: l.plannedDeparture || l.departure,
|
||||||
arrival: l.plannedArrival || l.arrival,
|
// arrival: l.plannedArrival || l.arrival, // sometimes differs on older journeys
|
||||||
line: l.line,
|
line: l.line,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ tap.test('db trip(): dynamic request formatting', (t) => {
|
||||||
|
|
||||||
const reqDbNav = profile.formatTripReq(ctx, tripIdHafas);
|
const reqDbNav = profile.formatTripReq(ctx, tripIdHafas);
|
||||||
delete reqDbNav.headers['X-Correlation-ID'];
|
delete reqDbNav.headers['X-Correlation-ID'];
|
||||||
const reqDbRegioGuide = profile.formatTripReq(ctx, tripIdRis);
|
// const reqDbRegioGuide = profile.formatTripReq(ctx, tripIdRis);
|
||||||
|
|
||||||
t.same(reqDbNav, reqDbNavExpected);
|
t.same(reqDbNav, reqDbNavExpected);
|
||||||
t.same(reqDbRegioGuide, reqDbRegioGuideExpected);
|
// t.same(reqDbRegioGuide, reqDbRegioGuideExpected);
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ const berlinWienQuery0 = Object.freeze(
|
||||||
einstiegsTypList: [
|
einstiegsTypList: [
|
||||||
'STANDARD',
|
'STANDARD',
|
||||||
],
|
],
|
||||||
|
fahrverguenstigungen: {
|
||||||
|
deutschlandTicketVorhanden: undefined,
|
||||||
|
nurDeutschlandTicketVerbindungen: undefined,
|
||||||
|
},
|
||||||
klasse: 'KLASSE_2',
|
klasse: 'KLASSE_2',
|
||||||
reiseHin: {
|
reiseHin: {
|
||||||
wunsch: {
|
wunsch: {
|
||||||
|
|
@ -48,6 +52,7 @@ const berlinWienQuery0 = Object.freeze(
|
||||||
verkehrsmittel: [
|
verkehrsmittel: [
|
||||||
'ALL',
|
'ALL',
|
||||||
],
|
],
|
||||||
|
alternativeHalteBerechnung: true,
|
||||||
zeitWunsch: {
|
zeitWunsch: {
|
||||||
reiseDatum: '2024-12-07T23:50:12+01:00',
|
reiseDatum: '2024-12-07T23:50:12+01:00',
|
||||||
zeitPunktArt: 'ABFAHRT',
|
zeitPunktArt: 'ABFAHRT',
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ const berlinWienQuery0 = Object.freeze(
|
||||||
sitzplatzOnly: false,
|
sitzplatzOnly: false,
|
||||||
bikeCarriage: false,
|
bikeCarriage: false,
|
||||||
reservierungsKontingenteVorhanden: false,
|
reservierungsKontingenteVorhanden: false,
|
||||||
nurDeutschlandTicketVerbindungen: false,
|
nurDeutschlandTicketVerbindungen: undefined,
|
||||||
deutschlandTicketVorhanden: false,
|
deutschlandTicketVorhanden: undefined,
|
||||||
maxUmstiege: null,
|
maxUmstiege: null,
|
||||||
zwischenhalte: null,
|
zwischenhalte: null,
|
||||||
minUmstiegszeit: 0,
|
minUmstiegszeit: 0,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue