diff --git a/p/db/index.js b/p/db/index.js new file mode 100644 index 00000000..7c897f20 --- /dev/null +++ b/p/db/index.js @@ -0,0 +1,109 @@ +'use strict' + +const crypto = require('crypto') + +const _formatStation = require('../../format/station') +const _parseLine = require('../../parse/line') +const {accessibility, bike} = require('../../format/filters') + +const modes = require('./modes') +const formatLoyaltyCard = require('./loyalty-cards').format + +const transformReqBody = (body) => { + body.client = {id: 'DB', v: '16040000', type: 'IPH', name: 'DB Navigator'} + body.ext = 'DB.R15.12.a' + body.ver = '1.15' + body.auth = {type: 'AID', aid: 'n91dB8Z77MLdoR0K'} + + return body +} + +const salt = 'bdI8UVj40K5fvxwf' +const transformReq = (req) => { + const hash = crypto.createHash('md5') + hash.update(req.body + salt) + + if (!req.query) req.query = {} + req.query.checksum = hash.digest('hex') + + return req +} + +const transformJourneysQuery = (query, opt) => { + const filters = query.jnyFltrL + if (opt.accessibility && accessibility[opt.accessibility]) { + filters.push(accessibility[opt.accessibility]) + } + if (opt.bike) filters.push(bike) + + query.trfReq = { + jnyCl: 2, // todo + tvlrProf: [{ + type: 'E', + redtnCard: opt.loyaltyCard + ? formatLoyaltyCard(opt.loyaltyCard) + : null + }], + cType: 'PK' + } + + return query +} + +const parseLine = (profile, l) => { + const res = _parseLine(profile, l) + + res.mode = res.product = null + if ('class' in res) { + const data = modes.bitmasks[parseInt(res.class)] + if (data) { + res.mode = data.mode + res.product = data.product + } + } + + return res +} + +const isIBNR = /^\d{6,}$/ +const formatStation = (id) => { + if (!isIBNR.test(id)) throw new Error('station ID must be an IBNR.') + return _formatStation(id) +} + +const defaultProducts = { + suburban: true, + subway: true, + tram: true, + bus: true, + ferry: true, + national: true, + nationalExp: true, + regional: true, + regionalExp: true +} + +// todo: find option for absolute number of results + +const dbProfile = { + timezone: 'Europe/Berlin', + endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe', + transformReqBody, + transformReq, + transformJourneysQuery, + + // todo: parseLocation + parseLine, + + formatStation, + formatProducts: (products) => { + products = Object.assign(Object.create(null), defaultProducts, products) + return { + type: 'PROD', + mode: 'INC', + value: modes.stringifyBitmask(products) + '' + } + } +} + +module.exports = dbProfile diff --git a/p/db/loyalty-cards.js b/p/db/loyalty-cards.js new file mode 100644 index 00000000..dfb583e9 --- /dev/null +++ b/p/db/loyalty-cards.js @@ -0,0 +1,30 @@ +'use strict' + +const c = { + NONE: Symbol('no loyaly card'), + BAHNCARD: Symbol('Bahncard'), + VORTEILSCARD: Symbol('VorteilsCard'), + HALBTAXABO: Symbol('HalbtaxAbo'), + VOORDEELURENABO: Symbol('Voordeelurenabo'), + SHCARD: Symbol('SH-Card'), + GENERALABONNEMENT: Symbol('General-Abonnement') +} + +// see https://gist.github.com/juliuste/202bb04f450a79f8fa12a2ec3abcd72d +const formatLoyaltyCard = (data) => { + if (data.type === c.BAHNCARD) { + if (data.discount === 25) return c.class === 1 ? 1 : 2 + if (data.discount === 50) return c.class === 1 ? 3 : 4 + } + if (data.type === c.VORTEILSCARD) return 9 + if (data.type === c.HALBTAXABO) return data.railplus ? 10 : 11 + if (data.type === c.VOORDEELURENABO) return data.railplus ? 12 : 13 + if (data.type === c.SHCARD) return 14 + if (data.type === c.GENERALABONNEMENT) return 15 + return 0 +} + +module.exports = { + data: c, + format: formatLoyaltyCard +} diff --git a/p/db/modes.js b/p/db/modes.js new file mode 100644 index 00000000..3b0a6a94 --- /dev/null +++ b/p/db/modes.js @@ -0,0 +1,113 @@ +'use strict' + +const m = { + nationalExp: { + bitmask: 1, + name: 'InterCityExpress', + short: 'ICE', + mode: 'train', + product: 'nationalExp' + }, + national: { + bitmask: 2, + name: 'InterCity & EuroCity', + short: 'IC/EC', + mode: 'train', + product: 'national' + }, + regionalExp: { + bitmask: 4, + name: 'InterRegio', + short: 'IR', + mode: 'train', + product: 'regionalExp' + }, + regional: { + bitmask: 8, + name: 'RegionalExpress & Regio', + short: 'RE/RB', + mode: 'train', + product: 'regional' + }, + suburban: { + bitmask: 16, + name: 'S-Bahn', + short: 'S', + mode: 'train', + product: 'suburban' + }, + bus: { + bitmask: 32, + name: 'Bus', + short: 'B', + mode: 'bus', + product: 'bus' + }, + ferry: { + bitmask: 64, + name: 'Ferry', + short: 'F', + mode: 'ferry', + product: 'ferry' + }, + subway: { + bitmask: 128, + name: 'U-Bahn', + short: 'U', + mode: 'train', + product: 'subway' + }, + tram: { + bitmask: 256, + name: 'Tram', + short: 'T', + mode: 'tram', + product: 'tram' + }, + taxi: { + bitmask: 512, + name: 'Group Taxi', + short: 'Taxi', + mode: null, // todo + product: 'taxi' + }, + unknown: { + bitmask: 0, + name: 'unknown', + short: '?', + product: 'unknown' + } +} + +m.bitmasks = [] +m.bitmasks[1] = m.nationalExp +m.bitmasks[2] = m.national +m.bitmasks[4] = m.regionalExp +m.bitmasks[8] = m.regional +m.bitmasks[16] = m.suburban +m.bitmasks[32] = m.bus +m.bitmasks[64] = m.ferry +m.bitmasks[128] = m.subway +m.bitmasks[256] = m.tram +m.bitmasks[512] = m.taxi + +// todo: move up +m.stringifyBitmask = (products) => { + let bitmask = 0 + for (let product in products) { + if (products[product] === true) bitmask += m[product].bitmask + } + return bitmask +} + +// todo: move up +m.parseBitmask = (bitmask) => { + let products = {}, i = 1 + do { + products[m.bitmasks[i].product] = !!(bitmask & i) + i *= 2 + } while (m.bitmasks[i] && m.bitmasks[i].product) + return products +} + +module.exports = m diff --git a/package.json b/package.json index 7ff792c0..a896a12a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "index.js", "lib", "parse", - "format" + "format", + "p" ], "author": "Jannis R ", "homepage": "https://github.com/derhuerst/hafas-client", diff --git a/test/db.js b/test/db.js index 9841817d..0bb8fcf7 100644 --- a/test/db.js +++ b/test/db.js @@ -5,6 +5,7 @@ const tape = require('tape') const isRoughlyEqual = require('is-roughly-equal') const createClient = require('..') +const dbProfile = require('../p/db') const { findStation, assertValidStation,