Closed derhuerst closed 1 year ago
@vkrause @Adwirawien @weiland @juliuste Do you know anything about this API?
Seems interesting, sadly I know as much as nothing! However, I think that it would be nice to know the endpoint for receiving journey info based on the ticket and name regardless of it being hafas related. If there only was a way to do this without purchasing an expensive ticket every time.
Does anyone have a 9-Euro Ticket linked to the DB Navigator app? It would be good to know if you are able to "book" trips with it and thus have a 9€ option of generating bigger amounts of reverse engineerable tickets even if its just for testing.
I personally don't know about the case with the 9 Euro Ticket. But from the Bahncard 100 I know that you can't "book" any journeys with it. If it's the same with 9 Euro I guess we are dependent on journeys that we have to really make.
If there only was a way to do this without purchasing an expensive ticket every time.
It seems to work with tickets/journeys from the past, at least with my Flex ticket from 2 weeks ago.
This would certainly be very interesting to have, I haven't looked into anything needing user authentication in more detail yet though.
From the structure in the APK I would have guessed that tickets are out of scope for Hafas though:
I looked into it with mitmproxy. The app is requesting fahrkarten.bahn.de and it seems to be an XML-RPC, probably the same that is used on this website https://fahrkarten.bahn.de/privatkunde/start/start.post?scope=bahnatsuche) Providing the ticket id and the last name, it is indeed possible to not only retrieve the full journey but every other data necessary to display the full online ticket like the full name, qr code and this weird generated background image that makes the ticket look 'real'.
I used the Navigator app on iOS 15 with mitmproxy in regular mode + ssl setup and tested this with old ticket numbers and last names, which worked well using the replay feature in mitmproxy. Exporting the curl command and executing it didnt work for me and only gives back an html page that displays an error. I dont know what the tnr
parameter does, it seems to be generated by the app but you can just use a random string here. If you want to test this with you own tickets, just replace YOUR_TICKETNUMBER
and YOUR_LASTNAME
with some valid information.
In the response I replaced some personal and irrelevant information (like css and base64 PNGs) with 'abc'.
POST /mobile/dbc/xs.go? HTTP/1.1
Host: fahrkarten.bahn.de
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Accept: */*
User-Agent: DB%20Navigator/13056071 CFNetwork/1333.0.4 Darwin/21.5.0
Accept-Language: de-DE,de;q=0.9
Content-Length: 255
Accept-Encoding: gzip, deflate, br
<?xml version="1.0"?>
<rqorderdetails version="1.0"><rqheader tnr="75547FAD-213F-3A42-AGC7-62DB101B" ts="2022-07-15T17:40:13" l="de" v="22060000" d="iPhone13,1" os="iOS_15.5" app="NAVIGATOR"/><rqorder on="YOUR_TICKETNUMBER"/><authname tln="YOUR_LASTNAME"/></rqorderdetails>
HTTP/1.1 200 OK
Date: Fri, 15 Jul 2022 17:59:18 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: AWSALB=abc; Expires=Fri, 22 Jul 2022 17:59:17 GMT; Path=/
Set-Cookie: AWSALBCORS=abc; Expires=Fri, 22 Jul 2022 17:59:17 GMT; Path=/; SameSite=None; Secure
Server: Apache
X-ORACLE-DMS-ECID: c45d72d8-6e6f-49eb-9756-cda3768e83e9-0098f908
X-ORACLE-DMS-RID: 0
Set-Cookie: DB4-pb-asid=abc; path=/;SameSite=none; secure; HttpOnly
Set-Cookie: DB4-pb-gsid=abc; domain=.bahn.de; path=/; SameSite=none;; secure; HttpOnly
Set-Cookie: DB4-pb-shopId=.4c; path=/; SameSite=none;; secure
Strict-Transport-Security: max-age=16070400; includeSubDomains
be2c
<?xml version="1.0" encoding="UTF-8"?><rporderdetails version="1.0"><rpheader tnr="65557FAD-203F-4A43-BFA7-24CB181A" ts="2022-07-15T19:59:17"/><order cdt="2022-05-25T17:25:02" cid="51451984" ddt="2023-07-25T17:25:02" fkat="5" hkey="d8cec5fd022181d8a76f39132bebf429" ldt="2022-05-25T17:25:05" on="abc" pg="1" sdt="2022-05-25T17:39:00" version="17" vfrom="2022-05-25T00:00:00" vto="2022-05-25T23:59:59" zweg="1"><schedulelist><out posnr="1"><txt>Zwickau(Sachs)Hbf 17:39 - Jena West 19:36 </txt><trainlist><train tid="C0-0.0" tn="RB 74069" type="T"><zugnr>74069</zugnr><gat>RB</gat><sci>N</sci><dep dt="2022-05-25T00:00:00" t="17:39:00"><n>Zwickau(Sachs)Hbf</n><nr>8010397</nr><ebhf_name>Zwickau(Sachs)Hbf</ebhf_name><ebhf_nr>8010397</ebhf_nr><ptf>4</ptf><plz>8056</plz><x>12474717</x><y>50714667</y></dep><arr dt="2022-05-25T00:00:00" t="17:54:00"><n>Glauchau(Sachs)</n><nr>8010129</nr><ebhf_name>Glauchau(Sachs)</ebhf_name><ebhf_nr>8010129</ebhf_nr><ptf>1</ptf><plz>8371</plz><x>12548645</x><y>50828525</y></arr></train><train tid="C0-0.1" tn="RE 3666" type="T"><zugnr>3666</zugnr><gat>RE</gat><sci>N</sci><dep dt="2022-05-25T00:00:00" t="18:07:00"><n>Glauchau(Sachs)</n><nr>8010129</nr><ebhf_name>Glauchau(Sachs)</ebhf_name><ebhf_nr>8010129</ebhf_nr><ptf>3</ptf><plz>8371</plz><x>12548645</x><y>50828525</y></dep><arr dt="2022-05-25T00:00:00" t="19:36:00"><n>Jena West</n><nr>8011957</nr><ebhf_name>Jena West</ebhf_name><ebhf_nr>8011957</ebhf_nr><ptf>1</ptf><plz>7745</plz><x>11577846</x><y>50923289</y></arr></train></trainlist></out></schedulelist><tcklist><tck posnr="2"><htdata><ht name="barcode" pos="1" type="image/png;base64">data:image/png;base64,
abc
</ht><ht name="httext" pos="2" type="text/html;base64">abc
</ht><ht name="htstyle" pos="2" type="text/css">
abc
</ht><ht name="sichtmerkmal" pos="10" type="image/png;base64">data:image/png;base64,abc
</ht><ht name="pass" pos="0" type="application/vnd.apple.pkpass;base64">abc
</ht></htdata><mtk dir="1" status="8"><txt>Einfache Fahrt, Regio120 Ticket, 2. Kl., 1 Erw., Zwickau(Sachs)/Jena#</txt><zb>N</zb><iss>80</iss><tkey>abc</tkey><ot_nr_hin>699347036</ot_nr_hin><reisender_vorname>abc</reisender_vorname><reisender_nachname>abc</reisender_nachname><nvplist><nvp name="k_erm_db">-------</nvp><nvp name="anzerw">1</nvp><nvp name="anzkind">0</nvp><nvp name="klasse">S2</nvp><nvp name="preisartcodedb">R120</nvp><nvp name="status">8</nvp></nvplist></mtk></tck></tcklist><posinfolist><posinfo dir="1" dirlabel="none" posnr="2" shownr="1002" state="8" type="TCK" typeinfo="0" vfo="2022-05-25T00:00:00" vto="2022-05-25T23:59:59"><childlist><posinfo dir="1" dirlabel="none" posnr="1" shownr="1100" state="8" type="EVA" typeinfo="0" vfo="2022-05-25T17:39:00" vto="2022-05-25T19:36:00"><childlist/></posinfo></childlist></posinfo></posinfolist><txt>Zwickau(Sachs)Hbf - Jena West</txt></order></rporderdetails>
0
Im kind of new to this stuff but for me it looks like its not related to hafas and they just made a quick way to use the 'Auftragssuche' natively in their navigator app.
Thanks! To expand on this, this is the response for my ticket, nicely formatted (and redacted a bit using *
):
curl -X POST 'https://fahrkarten.bahn.de/mobile/dbc/xs.go?' \
-H 'User-Agent: DB%20Navigator/13056071 CFNetwork/1333.0.4 Darwin/21.5.0' \
-H 'Accept-Language: de-DE,de;q=0.9' --compressed \
-d '<?xml version="1.0"?><rqorderdetails version="1.0"><rqheader tnr="foo" ts="2022-07-15T22:29:00" l="de" v="22060000" d="iPhone13,1" os="iOS_15.5" app="NAVIGATOR"/><rqorder on="GU****"/><authname tln="R******"/></rqorderdetails>' \
-s | xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<rporderdetails version="1.0">
<!-- `tnr` & `ts` are from my request -->
<rpheader tnr="foo" ts="2022-07-15T22:30:50"/>
<!--
`cdt`/`ddt`/`ldt`: booking date+time?
`on`: booking reference, from my request
`pg`: page?
`vfrom`: valid from
`vto`: valid until
`sdt`: journey start date+time
-->
<order
cdt="2022-06-29T17:13:21"
cid="4110****"
ddt="2023-08-29T17:13:21"
fkat="5"
hkey="b59b05275bc67983273eb7**********"
ldt="2022-06-29T17:13:22"
on="GU****"
pg="1"
sdt="2022-06-29T17:18:00"
version="17"
vfrom="2022-06-29T00:00:00"
vto="2022-06-30T23:59:59"
zweg="1"
>
<schedulelist>
<out posnr="1">
<txt>Düsseldorf Hbf 17:18 - Hauptbahnhof, Darmst 19:16 </txt>
<tp>
<depn>Düsseldorf+City</depn>
<arrn>Frankfurt(M)Flugh.</arrn>
</tp>
<trainlist>
<!-- `tid` looks a lot like the journey IDs & leg IDs from the bahn.de shop -->
<train tid="C0-0.0" tn="ICE 727" type="T">
<zugnr>727</zugnr>
<gat>ICE</gat>
<sci>Y</sci>
<dep dt="2022-06-29T00:00:00" t="17:18:00">
<n>Düsseldorf Hbf</n>
<nr>8000085</nr>
<ebhf_name>Düsseldorf Hbf</ebhf_name>
<ebhf_nr>8000085</ebhf_nr>
<ptf>15</ptf>
<plz>40210</plz>
<x>6794317</x>
<y>51219960</y>
</dep>
<arr dt="2022-06-29T00:00:00" t="18:33:00">
<n>Frankfurt(M) Flughafen Fernbf</n>
<nr>8070003</nr>
<ebhf_name>F-Flughafen Fernbf.</ebhf_name>
<ebhf_nr>8091032</ebhf_nr>
<ptf>Fern 4</ptf>
<plz>60549</plz>
<x>8570181</x>
<y>50053169</y>
</arr>
</train>
<!-- C0-0.1 is probably not used because it represents the walking leg in my original HAFAS journey? -->
<train tid="C0-0.2" tn="Bus AIR" type="T">
<zugnr>AIR</zugnr>
<gat>Bus</gat>
<dep dt="2022-06-29T00:00:00" t="18:44:00">
<n>Flughafen Terminal 1, Frankfurt a.M.</n>
<nr>102932</nr>
<ebhf_name>Flughafen Terminal 1</ebhf_name>
<ebhf_nr>102932</ebhf_nr>
<x>8571520</x>
<y>50051075</y>
</dep>
<arr dt="2022-06-29T00:00:00" t="19:16:00">
<n>Hauptbahnhof, Darmstadt</n>
<nr>104734</nr>
<ebhf_name>Hauptbahnhof, Darmst</ebhf_name>
<ebhf_nr>104734</ebhf_nr>
<plz>64293</plz>
<x>8631182</x>
<y>49872684</y>
</arr>
</train>
</trainlist>
</out>
</schedulelist>
<tcklist>
<tck posnr="2">
<htdata>
<ht name="barcode" pos="1" type="image/png;base64">data:image/png;base64,****</ht>
<ht name="httext" pos="2" type="text/html;base64">PHA+SGVycsKgIEphbm5pcyBSZWRtYW5uPGJyLz4KPGJyLz4KSUNFIEZhaHJrYXJ0
ZSwgRmxleHByZWlzIChFaW5mYWNoZSBGYWhydCk8YnIvPgpGYWhydGFudHJpdHQg
YW0gMjkuMDYuMjAyMjxici8+CkNpdHkgYW0gMjkuMDYuMjI8YnIvPgo8YnIvPgpL
bGFzc2U6IDIsIEVydy46IDE8YnIvPgpIaW5mYWhydDogRMO8c3NlbGRvcmYrQ2l0
eSAtIEZyYW5rZnVydChNKUZsdWdoLiwgbWl0IElDRTxici8+Cjxici8+CsOcYmVy
OiBWSUE6IEsqVFJPSSpTSUdCKk1UKkxNPGJyLz4KPGJyLz4KTnVyIGfDvGx0aWcg
bWl0IGFtdGxpY2hlbSBMaWNodGJpbGRhdXN3ZWlzICh6LkIuIFBlcnNvbmFsYXVz
d2VpcykuIERpZXNlciBpc3QgYmVpIGRlciBLb250cm9sbGUgdm9yenV6ZWlnZW4u
PGJyLz4KQmVpIEZhaHJrYXJ0ZW4gbWl0IEJhaG5DYXJkLVJhYmF0dCB6ZWlnZW4g
U2llIGJpdHRlIHp1c8OkdHpsaWNoIElocmUgZ8O8bHRpZ2UgQmFobkNhcmQgdm9y
Ljxici8+Cjxici8+ClN0b3JubyBrb3N0ZW5mcmVpIGJpcyAxIFRhZyB2b3IgMS4g
R2VsdHVuZ3N0YWcuPGJyLz4KPGJyLz4KQXVmdHJhZ3MtTnI6IEdVTlJMNDxici8+
Ckdlc2FtdHByZWlzOiA4MCw3MCBFVVI8YnIvPgpHZWJ1Y2h0IGFtIDI5LjA2LjIw
MjIgdW0gMTc6MTMgVWhyPGJyLz4KPC9wPg==
</ht>
<ht name="htstyle" pos="2" type="text/css">
body {
font-family: sans-serif;
font-size: 16px;
font-size: 1rem;
text-align: center;
}
.barcode img,
.sichtmerkmal img {
width: 90%;
}
p.AngebotBezeichnung,
.httext {
padding-left: 1em;
padding-right: 1em;
text-align: left;
line-height: 1.1;
word-break: break-word;
}
.httext h4 {
font-weight: bold;
font-size: 1.1rem;
}
p.CityMobilTarifangaben {
margin: 0 1em;
padding-left: 0;
padding-right: 0;
font-weight: bold;
}
.ShtAngebotbezeichnung {
font-weight: bold;
font-size: 1.1rem;
}
.OsaIPSIProduktbezeichnung {
font-weight: bold;
font-size: 1.1rem;
}
.OsaIPSIKondition {
font-size: 0.8rem;
}
</ht>
<ht name="sichtmerkmal" pos="10" type="image/png;base64">data:image/png;base64,****</ht>
</htdata>
<mtk dir="1" status="8">
<txt>Einfache Fahrt, Flexpreis, 2. Kl., 1 Erw., Düsseldorf+City/Frankfurt(M)Flugh.#</txt>
<zb>N</zb>
<!-- UIC country code? -->
<iss>80</iss>
<!-- booking reference -->
<tkey>GU****-3</tkey>
<ot_nr_hin>71301****</ot_nr_hin>
<reisender_vorname>Jannis</reisender_vorname>
<reisender_nachname>R******</reisender_nachname>
<nvplist>
<nvp name="k_erm_db">-------</nvp>
<nvp name="anzerw">1</nvp>
<nvp name="anzkind">0</nvp>
<!-- 2nd class? -->
<nvp name="klasse">S2</nvp>
<!-- normalpreis? -->
<nvp name="preisartcodedb">NP</nvp>
<nvp name="tpevahstart">Düsseldorf Hbf</nvp>
<nvp name="tpevahziel">Frankfurt(M) Flughafen Fernbf</nvp>
<nvp name="status">8</nvp>
<nvp name="citystart">Düsseldorf+City</nvp>
</nvplist>
</mtk>
</tck>
</tcklist>
<!-- how does this work? -->
<posinfolist>
<posinfo dir="1" dirlabel="none" posnr="2" shownr="1002" state="8" type="TCK" typeinfo="0" vfo="2022-06-29T00:00:00" vto="2022-06-30T23:59:59">
<childlist>
<posinfo dir="1" dirlabel="none" posnr="1" shownr="1100" state="8" type="EVA" typeinfo="0" vfo="2022-06-29T17:18:00" vto="2022-06-29T19:16:00">
<childlist/>
</posinfo>
</childlist>
</posinfo>
</posinfolist>
<txt>Düsseldorf Hbf - Hauptbahnhof, Darmst</txt>
</order>
</rporderdetails>
The <ht rel="httext">
element contains this:
<p>Herr Jannis R******<br/>
<br/>
ICE Fahrkarte, Flexpreis (Einfache Fahrt)<br/>
Fahrtantritt am 29.06.2022<br/>
City am 29.06.22<br/>
<br/>
Klasse: 2, Erw.: 1<br/>
Hinfahrt: Düsseldorf+City - Frankfurt(M)Flugh., mit ICE<br/>
<br/>
Über: VIA: K*TROI*SIGB*MT*LM<br/>
<br/>
Nur gültig mit amtlichem Lichtbildausweis (z.B. Personalausweis). Dieser ist bei der Kontrolle vorzuzeigen.<br/>
Bei Fahrkarten mit BahnCard-Rabatt zeigen Sie bitte zusätzlich Ihre gültige BahnCard vor.<br/>
<br/>
Storno kostenfrei bis 1 Tag vor 1. Geltungstag.<br/>
<br/>
Auftrags-Nr: GU****<br/>
Gesamtpreis: **,** EUR<br/>
Gebucht am 29.06.2022 um 17:13 Uhr<br/>
</p>
Given that this format doesn't seem to be HAFAS-related, let's implement a client in a separate repo.
Does anyone want to do it? I'll happily create a repo in the public-transport
org and transfer this Issue there.
Nice find, works here too! I somehow had always assumed this needs full user authentication, apparently not :D
The XML requests match strings I see in the db.bahn.dbtickets.* subtree of the DB Navigator APK, which then would suggest we can also download BahnCards that way.
Does anyone want to do it? I'll happily create a repo in the public-transport org and transfer this Issue there.
I can do it. I allready have a simple request in JS that just returns the whole xml.
I can do it. I allready have a simple request in JS that just returns the whole xml.
Great!
I think it would be great to parse the data into a usable format that we can keep (mostly) stable if their API changes. I (try to) do it the same way with generate-db-shop-urls
.
Ok cool. Are there any good practices regarding the data structure that should be returned? Cause that would be the next step I guess. Here is what I got so far: db-tickets
Are there any good practices regarding the data structure that should be returned?
It's definitely good practice not to make up yet another structure (as some others and I did too a while ago tbh 😬).
The hafas-client
ecosystem, as well as some ports to other languages, follow the Friendly Public Transport Format (FPTF) (with some unnecessary exceptions).
There also many tools that follow one of OpenTripPlanner's APIs' semantics, or navitia's /journeys
's semantics.
The EU made Transmodel, a semantic model defining many aspects of public transportation, that may gain momentum over the next years. NeTEx is based on it.
Unfortunately, I can't give a clear recommendation which one to use; I guess it comes down to which way of representing the data you like most, and what makes sense given the existing data model in the tickets API.
Just throwing in my cents, in case you haven't found already, the API also yields other types of product, like seat reservations.
@schildbach Thanks for the hint. Do you have examples that you can comment, either here or in the db-tickets
repo?
Just published an npm package with a basic, stable version. Feel free to add it to the public-transport org. db-tickets
The DB Navigator app has the option, given a booking reference and the last name of the person who booked, to retrieve the ticket(s) as well as the journey it has been booked for.
Because the journey UI looks as in the rest of the app, I assume that the API behind this API is HAFAS-flavored.
What needs to be done to find this API, as documented in the (somewhat unrelated) Writing a profile guide:
Please share the recorded requests in their entirety, if possible, but make sure to redact personal details, or e.g. tokens that they can be retrieved with! Then, we can figure out if a client for this API belongs in here, or in a separate library.