Skip to content

Commit

Permalink
add support for BestPriceSearch
Browse files Browse the repository at this point in the history
  • Loading branch information
bergmannjg committed Jun 18, 2023
1 parent 4cb7062 commit ce4b5b5
Show file tree
Hide file tree
Showing 8 changed files with 4,835 additions and 1 deletion.
75 changes: 74 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,79 @@ const createClient = (profile, userAgent, opt = {}) => {
}
}

const refreshJourney = async (refreshToken, opt = {}) => {
const bestPrices = async (from, to, opt = {}) => {
from = profile.formatLocation(profile, from, 'from')
to = profile.formatLocation(profile, to, 'to')

opt = Object.assign({
via: null, // let journeys pass this station?
transfers: -1, // maximum nr of transfers
bike: false, // only bike-friendly journeys
tickets: false, // return tickets?
polylines: false, // return leg shapes?
subStops: false, // parse & expose sub-stops of stations?
entrances: false, // parse & expose entrances of stops/stations?
remarks: true, // parse & expose hints & warnings?
scheduledDays: false, // parse & expose dates each journey is valid on?
}, opt)
if (opt.via) opt.via = profile.formatLocation(profile, opt.via, 'opt.via')

let when = new Date();
if (opt.departure !== undefined && opt.departure !== null) {
when = new Date(opt.departure)
if (Number.isNaN(+when)) throw new TypeError('opt.departure is invalid')
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
if (today > when) {
throw new TypeError('opt.departure date older than current date.')
}
}

const filters = [
profile.formatProductsFilter({profile}, opt.products || {})
]
if (
opt.accessibility &&
profile.filters &&
profile.filters.accessibility &&
profile.filters.accessibility[opt.accessibility]
) {
filters.push(profile.filters.accessibility[opt.accessibility])
}

const query = {
maxChg: opt.transfers,
depLocL: [from],
viaLocL: opt.via ? [{loc: opt.via}] : [],
arrLocL: [to],
jnyFltrL: filters,
getTariff: !!opt.tickets,

getPolyline: !!opt.polylines
}
query.outDate = profile.formatDate(profile, when)

const {res, common} = await profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'},
meth: 'BestPriceSearch',
req: profile.transformJourneysQuery({profile, opt}, query)
})
if (!Array.isArray(res.outConL)) return {}
// todo: outConGrpL

const ctx = {profile, opt, common, res}
const journeys = res.outConL.map(j => profile.parseJourney(ctx, j))
const bestPrices = res.outDaySegL.map(j => profile.parseBestPrice(ctx, j, journeys))

return {
bestPrices,
realtimeDataUpdatedAt: res.planrtTS && res.planrtTS !== '0'
? parseInt(res.planrtTS)
: null,
}
}

const refreshJourney = async (refreshToken, opt = {}) => {
if ('string' !== typeof refreshToken || !refreshToken) {
throw new TypeError('refreshToken must be a non-empty string.')
}
Expand Down Expand Up @@ -791,6 +863,7 @@ const createClient = (profile, userAgent, opt = {}) => {
nearby,
serverInfo,
}
if (profile.bestPrices) client.bestPrices = bestPrices
if (profile.trip) client.trip = trip
if (profile.radar) client.radar = radar
if (profile.refreshJourney) client.refreshJourney = refreshJourney
Expand Down
3 changes: 3 additions & 0 deletions lib/default-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {parseArrival} from '../parse/arrival.js'
import {parseTrip} from '../parse/trip.js'
import {parseJourneyLeg} from '../parse/journey-leg.js'
import {parseJourney} from '../parse/journey.js'
import {parseBestPrice} from '../parse/bestprice.js'
import {parseLine} from '../parse/line.js'
import {parseLocation} from '../parse/location.js'
import {parseCommonData as parseCommon} from '../parse/common.js'
Expand Down Expand Up @@ -91,6 +92,7 @@ const defaultProfile = {
parseTrip,
parseJourneyLeg,
parseJourney,
parseBestPrice,
parseLine,
parseStationName: (_, name) => name,
parseLocation,
Expand Down Expand Up @@ -123,6 +125,7 @@ const defaultProfile = {
// `departures()` method: support for `stbFltrEquiv` field?
departuresStbFltrEquiv: false,

bestPrices: false,
trip: false,
radar: false,
refreshJourney: true,
Expand Down
1 change: 1 addition & 0 deletions p/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ const profile = {
formatStation,

refreshJourneyUseOutReconL: true,
bestPrices: true,
trip: true,
journeysFromTrip: true,
radar: true,
Expand Down
26 changes: 26 additions & 0 deletions parse/bestprice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

const parseBestPrice = (ctx, outDaySeg, journeys) => {
const {profile, res} = ctx

const bpjourneys = outDaySeg.conRefL
? outDaySeg.conRefL
.map(i => journeys.find(j => j.refreshToken == res.outConL[i].ctxRecon))
.filter(j => !!j)
: []

const amount = outDaySeg.bestPrice.amount / 100
const currency = bpjourneys?.[0]?.price?.currency;

const result = {
journeys: bpjourneys,
fromDate: profile.parseDateTime(ctx, outDaySeg.fromDate, outDaySeg.fromTime),
toDate: profile.parseDateTime(ctx, outDaySeg.toDate, outDaySeg.toTime),
bestPrice: amount > 0 && currency ? { amount, currency} : undefined
}

return result
}

export {
parseBestPrice,
}
40 changes: 40 additions & 0 deletions test/db-bestprice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// todo: use import assertions once they're supported by Node.js & ESLint
// https://github.com/tc39/proposal-import-assertions
import {createRequire} from 'module'
const require = createRequire(import.meta.url)

import tap from 'tap'

import {createClient} from '../index.js'
import {profile as rawProfile} from '../p/db/index.js'
const response = require('./fixtures/db-bestprice.json')
import {dbBestPrices as expected} from './fixtures/db-bestprice.js'

const client = createClient(rawProfile, 'public-transport/hafas-client:test')
const {profile} = client

const opt = {
via: null,
transfers: -1,
transferTime: 0,
accessibility: 'none',
bike: false,
tickets: true,
polylines: true,
remarks: true,
walkingSpeed: 'normal',
startWithWalking: true,
departure: '2023-06-15',
products: {}
}

tap.test('parses a bestprice with a DEVI leg correctly (DB)', (t) => {
const res = response.svcResL[0].res
const common = profile.parseCommon({profile, opt, res})
const ctx = {profile, opt, common, res}
const journeys = res.outConL.map(j => profile.parseJourney(ctx, j))
const bestPrices = res.outDaySegL.map(j => profile.parseBestPrice(ctx, j, journeys))

t.same(bestPrices, expected.bestPrices)
t.end()
})
Loading

0 comments on commit ce4b5b5

Please sign in to comment.