From 9e75f4234649d63448886920b200b173af219076 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Sat, 15 Feb 2020 19:16:18 +0000 Subject: [PATCH] DB: parse Reisezentrum opening hours & station facilities --- p/db/index.js | 102 ++++++++++++++++++++++++++++++++++++++++++++++++- p/db/readme.md | 51 +++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/p/db/index.js b/p/db/index.js index 51e59751..c298f09a 100644 --- a/p/db/index.js +++ b/p/db/index.js @@ -1,6 +1,9 @@ 'use strict' const trim = require('lodash/trim') +const uniqBy = require('lodash/uniqBy') +const slugg = require('slugg') +const without = require('lodash/without') const {parseHook} = require('../../lib/profile-hooks') const _parseJourney = require('../../parse/journey') @@ -9,6 +12,7 @@ const _parseLine = require('../../parse/line') const _parseArrival = require('../../parse/arrival') const _parseDeparture = require('../../parse/departure') const _parseHint = require('../../parse/hint') +const _parseLocation = require('../../parse/location') const _formatStation = require('../../format/station') const {bike} = require('../../format/filters') @@ -23,12 +27,101 @@ const transformReqBody = (ctx, body) => { body.client = {id: 'DB', v: '16040000', type: 'IPH', name: 'DB Navigator'} body.ext = 'DB.R19.04.a' - body.ver = '1.16' + body.ver = '1.15' body.auth = {type: 'AID', aid: 'n91dB8Z77MLdoR0K'} return body } +const slices = (n, arr) => { + const initialState = {slices: [], count: Infinity} + return arr.reduce(({slices, count}, item) => { + if (count >= n) { + slices.push([item]) + count = 1 + } else { + slices[slices.length - 1].push(item) + count++ + } + return {slices, count} + }, initialState).slices +} + +const parseGrid = (g) => { + // todo: g.type, e.g. `S` + return { + title: g.title, + rows: slices(g.nCols, g.itemL.map(item => item.msgL[0])) + } +} + +const ausstattungKeys = Object.assign(Object.create(null), { + '3-s-zentrale': '3SZentrale', + 'parkplatze': 'parkingLots', + 'fahrrad-stellplatze': 'bicycleParkingRacks', + 'opnv-anbindung': 'localPublicTransport', + 'wc': 'toilets', + 'schliessfacher': 'lockers', + 'reisebedarf': 'travelShop', + 'stufenfreier-zugang': 'stepFreeAccess', + 'ein-umsteigehilfe': 'boardingAid', + 'taxi-am-bahnhof': 'taxis' +}) +const parseAusstattungVal = (val) => { + val = val.toLowerCase() + return val === 'ja' ? true : (val === 'nein' ? false : val) +} + +const parseAusstattungGrid = (g) => { + // filter duplicate hint rows + const rows = uniqBy(g.rows, ([key, val]) => key + ':' + val) + + const res = {raw: rows} + for (let [key, val] of rows) { + key = ausstattungKeys[slugg(key)] + if (key) res[key] = parseAusstattungVal(val) + } + return res +} + +const parseReisezentrumÖffnungszeiten = (g) => { + const res = {} + for (const [dayOfWeek, val] of g.rows) res[dayOfWeek] = val + res.raw = g.rows + return res +} + +const parseLocWithDetails = ({parsed, common}, l) => { + if (!parsed) return parsed + if (parsed.type !== 'stop' && parsed.type !== 'station') return parsed + + if (Array.isArray(l.gridL)) { + const resolveCell = cell => 'hint' in cell ? cell.hint.text : cell + const resolveCells = grid => ({ + ...grid, + rows: grid.rows.map(row => row.map(resolveCell)) + }) + + let grids = l.gridL + .map(grid => parseGrid(grid, common)) + .map(resolveCells) + + const ausstattung = grids.find(g => slugg(g.title) === 'ausstattung') + if (ausstattung) { + parsed.facilities = parseAusstattungGrid(ausstattung) + } + const öffnungszeiten = grids.find(g => slugg(g.title) === 'offnungszeiten-reisezentrum') + if (öffnungszeiten) { + parsed.reisezentrumOpeningHours = parseReisezentrumÖffnungszeiten(öffnungszeiten) + } + + grids = without(grids, ausstattung, öffnungszeiten) + if (grids.length > 0) parsed.grids = grids + } + + return parsed +} + // https://www.bahn.de/p/view/service/buchung/auslastungsinformation.shtml const loadFactors = [] loadFactors[1] = 'low-to-medium' @@ -324,6 +417,11 @@ const codesByText = Object.assign(Object.create(null), { }) const parseHintByCode = ({parsed}, raw) => { + // plain-text hints used e.g. for stop metadata + if (raw.type === 'K') { + return {type: 'hint', text: raw.txtN} + } + if (raw.type === 'A') { const hint = hintsByCode[raw.code && raw.code.trim().toLowerCase()] if (hint) { @@ -360,7 +458,7 @@ const dbProfile = { products: products, - // todo: parseLocation + parseLocation: parseHook(_parseLocation, parseLocWithDetails), parseJourney: parseHook(_parseJourney, parseJourneyWithPrice), parseJourneyLeg: parseHook(_parseJourneyLeg, parseJourneyLegWithLoadFactor), parseLine: parseHook(_parseLine, parseLineWithAdditionalName), diff --git a/p/db/readme.md b/p/db/readme.md index 451884ae..01adad00 100644 --- a/p/db/readme.md +++ b/p/db/readme.md @@ -19,6 +19,57 @@ const client = createClient(dbProfile, 'my-awesome-program') - supports [their loyalty cards](https://en.wikipedia.org/wiki/Deutsche_Bahn#Tickets) with `journey()` - parses *DB*-specific products (such as *InterCity-Express*) - exposes the cheapest ticket price for a `journey` +- parses [*DB*-specific station info](#additional-station-info) + +### additional station info + +With the `db` profile, `hafas-client` will return more station information whenever the endpoint provides it: + +```js +{ + type: 'station', + id: '8004585', + name: 'Oberstdorf', + // … + facilities: { + '3SZentrale': '089/13081055', + parkingLots: true, + bicycleParkingRacks: true, + localPublicTransport: true, + toilets: true, + lockers: true, + travelShop: true, + stepFreeAccess: true, + boardingAid: 'ja, um voranmeldung unter 01806 512 512* wird gebeten', + taxis: true + }, + reisezentrumOpeningHours: { + Mo: '08:00-18:00', + Di: '08:00-18:00', + Mi: '08:00-18:00', + Do: '08:00-18:00', + Fr: '08:00-18:00', + Sa: '09:00-14:00', + So: '09:00-14:00' + }, + // … + stops: [{ + type: 'stop', + id: '965503', + name: 'Busbahnhof, Oberstdorf', + // … + reisezentrumOpeningHours: { + Mo: '08:00-18:00', + Di: '08:00-18:00', + Mi: '08:00-18:00', + Do: '08:00-18:00', + Fr: '08:00-18:00', + Sa: '09:00-14:00', + So: '09:00-14:00' + } + }] +} +``` ## Using the `loyaltyCard` option