-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from arnebr/main
Adding support for new ticket numbers
- Loading branch information
Showing
11 changed files
with
1,265 additions
and
985 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,10 @@ | ||
import queryTicket from './index.js' | ||
import util from 'util' | ||
import process from 'process' | ||
|
||
// request data with ticket number and lastname | ||
const ticketNumber = process.env.TICKET_NR || 'XXXXXX' | ||
const lastname = process.env.LAST_NAME || 'Mustermann' | ||
|
||
console.log('testing with ' + ticketNumber + ' ' + lastname) | ||
|
||
const data = await queryTicket(ticketNumber, lastname) | ||
console.log(util.inspect(data, false, null)) | ||
// console.log(util.inspect(data, false, null)) | ||
console.log(JSON.stringify(data)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { requestBackend } from './requestBackend.js' | ||
/** | ||
* findTicket function | ||
* @param string | DB ticket number (Auftragsnummer 12 digits) | ||
* @param lastName | last name of purchaser | ||
* @return Promise | fulfills with an object that contains the ticket data | ||
*/ | ||
export const findTicket = async (ticketNumber, lastName) => { | ||
//make a request to the endpoint with the following body: <?xml version="1.0"?> | ||
//<rqfindorder version="1.0"><rqheader tnr="xxx" ts="2023-08-11T11:36:51" l="de" v="23080000" d="xxx" os="xxx" app="NAVIGATOR"/><rqorder on="INT-UP-TO-12"/><authname tln="Lastname"/></rqfindorder> | ||
const reqBody = '<?xml version="1.0"?><rqfindorder version="1.0">' | ||
+ '<rqheader l="de" v="23080000" d="iPhone16.4.1" os="iOS_15.7.5" app="NAVIGATOR"/>' | ||
+ '<rqorder on="' + ticketNumber + '"/><authname tln="' + lastName + '"/></rqfindorder>' | ||
return requestBackend(reqBody).then(json => { | ||
const data = json.rporderheadlist | ||
|
||
const _order = data['orderheadlist'][0]['orderhead'][0]['$'] | ||
return _order}) | ||
} | ||
|
||
|
||
export default findTicket |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,110 +1,80 @@ | ||
import fetch from 'node-fetch' | ||
import { parseString } from 'xml2js' | ||
import { parseLegs, price } from './parse.js' | ||
|
||
const endpoint = 'https://fahrkarten.bahn.de/mobile/dbc/xs.go?' | ||
import requestBackend from './requestBackend.js'; | ||
|
||
/** | ||
* queryTicket function | ||
* parseTicket | ||
* | ||
* @param string | DB ticket number (Auftragsnummer) | ||
* @param lastName | last name of purchaser | ||
* @return Promise | fulfills with an object that contains the ticket data | ||
* @param string | ticket API response body, JSON-decoded | ||
* @return object | the ticket data | ||
*/ | ||
const queryTicket = async (ticketNumber, lastName) => { | ||
const parseTicket = (json) => { | ||
const data = json.rporderdetails | ||
|
||
// selectors | ||
const _order = data['order'][0]['$'] | ||
const _tck = data['order'][0]['tcklist']?.[0]['tck'][0] | ||
const _ticket = _tck?.['mtk'][0] | ||
let _encoded_price = _tck['htdata'][0]['ht'][0]['_'] | ||
//if _tck['htdata'][0]['ht'][0]['_'] starts with data:image/png;base64, then it is a old ticket, we need to check for that | ||
if(_encoded_price.startsWith('data:image/png;base64,')){ | ||
_encoded_price = _tck['htdata'][0]['ht'][1]['_'] // revert back to the old price format | ||
} | ||
|
||
const reqBody = '<?xml version="1.0"?><rqorderdetails version="1.0">' | ||
+ '<rqheader tnr="61743782011" ts="2022-07-15T22:29:00" l="de" v="22060000" d="iPhone13,1" os="iOS_15.5" app="NAVIGATOR"/>' | ||
+ '<rqorder on="' + ticketNumber + '"/><authname tln="' + lastName + '"/></rqorderdetails>' | ||
const _price = _tck ? { | ||
amount: price(_encoded_price), | ||
currency: 'EUR' | ||
}: undefined | ||
|
||
return fetch(endpoint, { | ||
method: 'POST', | ||
headers: { | ||
'User-Agent': 'github.com/envake/db-tickets', | ||
'Accept-Language': 'de-DE,de;q=0.9' | ||
return { | ||
// doesn't follow a standard by now and just dumps some info | ||
order: { | ||
ticketNumber: _order['on'], | ||
bookingDate: _order['cdt'], | ||
validFrom: _order['vfrom'], | ||
validUntil: _order['vto'], | ||
journeyStart: _order['sdt'], | ||
firstName: _ticket?.['reisender_vorname'][0], | ||
lastName: _ticket?.['reisender_nachname'][0], | ||
text: _ticket?.['txt'][0], | ||
class: _ticket?.['nvplist'][0]['nvp'][3]['_'] // could be wrong | ||
}, | ||
body: reqBody | ||
}) | ||
.then(response => { | ||
if (!response.ok) { | ||
throw new HTTPResponseError(response) | ||
} | ||
return response.text() | ||
}) | ||
.then(body => { | ||
return new Promise((resolve, reject) => parseString(body, (err, result) => { | ||
if (err) { | ||
reject(err) | ||
} | ||
else { | ||
resolve(result) | ||
} | ||
})) | ||
}) | ||
.then(json => { | ||
if (json['rperror']) { | ||
throw new XMLRPCError(json['rperror']['error']) | ||
} | ||
|
||
const data = json.rporderdetails | ||
console.log(JSON.stringify(data)) | ||
|
||
// selectors | ||
const _order = data['order'][0]['$'] | ||
const _tck = data['order'][0]['tcklist']?.[0]['tck'][0] | ||
const _ticket = _tck?.['mtk'][0] | ||
// follows FPTF Journey, https://github.com/public-transport/friendly-public-transport-format/blob/master/spec/readme.md#journey | ||
journey: { | ||
type: 'journey', | ||
id: _order['cid'], // unique, required, use cid as id here? | ||
legs: parseLegs(data['order'][0]['schedulelist'][0]['out'][0]['trainlist'][0]['train']), | ||
price: _price | ||
}, | ||
|
||
const _price = _tck ? { | ||
amount: price(_tck['htdata'][0]['ht'][1]['_']), | ||
currency: 'EUR' | ||
}: undefined | ||
...(data['order'][0]['schedulelist'][0]['ret'] && { returnJourney: { | ||
type: 'journey', | ||
id: _order['cid'], // unique, required, ! same as outward journey? | ||
legs: parseLegs(data['order'][0]['schedulelist'][0]['ret'][0]['trainlist'][0]['train']), | ||
price: _price | ||
}}) | ||
} | ||
} | ||
|
||
return { | ||
// doesn't follow a standard by now and just dumps some info | ||
order: { | ||
ticketNumber: _order['on'], | ||
bookingDate: _order['cdt'], | ||
validFrom: _order['vfrom'], | ||
validUntil: _order['vto'], | ||
journeyStart: _order['sdt'], | ||
firstName: _ticket?.['reisender_vorname'][0], | ||
lastName: _ticket?.['reisender_nachname'][0], | ||
text: _ticket?.['txt'][0], | ||
class: _ticket?.['nvplist'][0]['nvp'][3]['_'] // could be wrong | ||
}, | ||
/** | ||
* queryTicket function | ||
* | ||
* @param string | DB ticket number (Auftragsnummer) | ||
* @param lastName | last name of purchaser | ||
* @param kwid | kwid of the ticket (only needed for tickets from the new system) | ||
* @return Promise | fulfills with an object that contains the ticket data | ||
*/ | ||
const queryTicket = async (ticketNumber, lastName,kwid=null) => { | ||
|
||
// follows FPTF Journey, https://github.com/public-transport/friendly-public-transport-format/blob/master/spec/readme.md#journey | ||
journey: { | ||
type: 'journey', | ||
id: _order['cid'], // unique, required, use cid as id here? | ||
legs: parseLegs(data['order'][0]['schedulelist'][0]['out'][0]['trainlist'][0]['train']), | ||
price: _price | ||
}, | ||
let reqBody = `<?xml version="1.0"?> <rqorderdetails version="2.0"> <rqheader l="de" v="23080000" d="iPhone16.4.1" os="iOS_15.7.5" app="NAVIGATOR" /> <rqorder on="${ticketNumber}" ${kwid ? `kwid="${kwid}" ` : ''}/> <authname tln="${lastName}"/> </rqorderdetails> ` | ||
|
||
...(data['order'][0]['schedulelist'][0]['ret'] && { returnJourney: { | ||
type: 'journey', | ||
id: _order['cid'], // unique, required, ! same as outward journey? | ||
legs: parseLegs(data['order'][0]['schedulelist'][0]['ret'][0]['trainlist'][0]['train']), | ||
price: _price | ||
}}) | ||
} | ||
return await requestBackend(reqBody) | ||
.then(json => { | ||
return parseTicket(json) | ||
}) | ||
} | ||
|
||
class HTTPResponseError extends Error { | ||
constructor(response, ...args) { | ||
super(`HTTP Error Response: ${response.status} ${response.statusText}`, ...args) | ||
this.response = response | ||
} | ||
} | ||
|
||
class XMLRPCError extends Error { | ||
constructor(response, ...args) { | ||
super(`XML Error Response: | ||
${response[0]['$']['nr']} | ||
${response[0]['txt'][0]}`, ...args) | ||
this.response = response | ||
} | ||
export { | ||
parseTicket, | ||
queryTicket, | ||
} | ||
|
||
export default queryTicket |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import fetch from 'node-fetch' | ||
import { parseString } from 'xml2js' | ||
|
||
const endpoint = 'https://fahrkarten.bahn.de/mobile/dbc/xs.go?' | ||
|
||
/** | ||
* requestBackend function, that is used to make a request to the backend | ||
* @param string | body of the request | ||
* @return Promise | fulfills with an object that contains the data from the request | ||
*/ | ||
export const requestBackend = async (reqBody) => { | ||
return fetch(endpoint, { | ||
method: 'POST', | ||
headers: { | ||
'User-Agent': 'github.com/public-transport/db-tickets', | ||
'Accept-Language': 'de-DE,de;q=0.9' | ||
}, | ||
body: reqBody | ||
}).then(response => { | ||
if (!response.ok) { | ||
const err = new HTTPResponseError(response) | ||
err.url = endpoint | ||
err.requestBody = reqBody | ||
throw err | ||
} | ||
return response.text() | ||
}).then(body => { | ||
return new Promise((resolve, reject) => parseString(body, (err, result) => { | ||
if (err) { | ||
reject(err) | ||
} | ||
else { | ||
resolve(result) | ||
} | ||
})) | ||
}).then(json => { | ||
if (json['rperror']) { | ||
throw new XMLRPCError(json['rperror']['error']) | ||
} | ||
return json | ||
}) | ||
} | ||
class HTTPResponseError extends Error { | ||
constructor(response, ...args) { | ||
super(`HTTP Error Response: ${response.status} ${response.statusText}`, ...args) | ||
this.response = response | ||
} | ||
} | ||
|
||
class XMLRPCError extends Error { | ||
constructor(response, ...args) { | ||
super(`XML Error Response: | ||
${response[0]['$']['nr']} | ||
${response[0]['txt'][0]}`, ...args) | ||
this.response = response | ||
} | ||
} | ||
export default requestBackend |
Oops, something went wrong.