Skip to content

Commit

Permalink
dbnav profile: locations, nearby
Browse files Browse the repository at this point in the history
  • Loading branch information
traines-source committed Dec 21, 2024
1 parent ad6c356 commit debc1ee
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 34 deletions.
1 change: 1 addition & 0 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {createHafasRestApi as createApi} from 'hafas-rest-api';
import {loyaltyCardParser} from 'db-rest/lib/loyalty-cards.js';
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js';

// TODO product support for nearby etc?
const mapRouteParsers = (route, parsers) => {
if (!route.includes('journey')) {
return parsers;
Expand Down
3 changes: 3 additions & 0 deletions format/products-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const formatProductsFilter = (ctx, filter, key = 'vendo') => {
if (!foundDeselected && key == 'ris') {
return undefined;
}
if (!foundDeselected && key == 'dbnav') {
return ['ALL'];
}

return products;
};
Expand Down
19 changes: 0 additions & 19 deletions format/station-board-req.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,6 @@ const formatStationBoardReq = (ctx, station, type) => {
};
};

/*
TODO separate DB Nav profile?
const formatStationBoardReq = (ctx, station, type) => {
const { profile, opt } = ctx;
return {
endpoint: profile.boardEndpoint,
path: type == 'departures' ? 'abfahrt' : 'ankunft',
body: { "anfragezeit": profile.formatTime(profile, opt.when), "datum": profile.formatDate(profile, opt.when), "ursprungsBahnhofId": profile.formatStation(station).lid, "verkehrsmittel": profile.formatProductsFilter(ctx, opt.products || {}, 'dbnav') },
method: 'POST',
header: {
'Accept': 'application/x.db.vendo.mob.bahnhofstafeln.v2+json',
'X-Correlation-ID': 'null',
'Content-Type': 'application/x.db.vendo.mob.bahnhofstafeln.v2+json'
}
};
};
*/

/*
TODO separate RIS::Boards profile?
const formatRisStationBoardReq = (ctx, station, type) => {
Expand Down
11 changes: 8 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ const createClient = (profile, userAgent, opt = {}) => {
const {res} = await profile.request({profile, opt}, userAgent, req);

const ctx = {profile, opt, common, res};
const results = (res[resultsField] || res.items).map(res => parse(ctx, res)); // todo sort?
const results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen)
.map(res => parse(ctx, res)); // TODO sort?, slice

return {
[resultsField]: results,
Expand Down Expand Up @@ -189,7 +190,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatJourneysReq({profile, opt}, from, to, when, outFrwd, journeysRef);
const {res} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res};
const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
const verbindungen = Number.isInteger(opt.results) ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
const journeys = verbindungen
.map(j => profile.parseJourney(ctx, j));

Expand Down Expand Up @@ -246,7 +247,11 @@ const createClient = (profile, userAgent, opt = {}) => {
const {res} = await profile.request({profile, opt}, userAgent, req);

const ctx = {profile, opt, common, res};
return res.map(loc => profile.parseLocation(ctx, loc));
const results = res.map(loc => profile.parseLocation(ctx, loc));

return Number.isInteger(opt.results)
? results.slice(0, opt.results)
: results;
};

const stop = async (stop, opt = {}) => { // TODO
Expand Down
9 changes: 6 additions & 3 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const request = async (ctx, userAgent, reqData) => {
const endpoint = reqData.endpoint;
delete reqData.endpoint;
const rawReqBody = profile.transformReqBody(ctx, reqData.body);
// console.log(rawReqBody, JSON.stringify(rawReqBody.req.reisende));

const req = profile.transformReq(ctx, {
agent: getAgent(),
method: reqData.method,
Expand All @@ -121,7 +121,10 @@ const request = async (ctx, userAgent, reqData) => {
query: reqData.query,
});

const url = endpoint + (reqData.path || '') + '?' + stringify(req.query, {arrayFormat: 'brackets', encodeValuesOnly: true});
const url = endpoint + (reqData.path || '');
if (query) {

Check failure on line 125 in lib/request.js

View workflow job for this annotation

GitHub Actions / unit-tests (20.x)

'query' is not defined

Check failure on line 125 in lib/request.js

View workflow job for this annotation

GitHub Actions / unit-tests (22.x)

'query' is not defined
url += '?' + stringify(req.query, {arrayFormat: 'brackets', encodeValuesOnly: true});

Check failure on line 126 in lib/request.js

View workflow job for this annotation

GitHub Actions / unit-tests (20.x)

'url' is constant

Check failure on line 126 in lib/request.js

View workflow job for this annotation

GitHub Actions / unit-tests (22.x)

'url' is constant
}
const reqId = randomBytes(3)
.toString('hex');
const fetchReq = new Request(url, req);
Expand All @@ -147,7 +150,7 @@ const request = async (ctx, userAgent, reqData) => {
let cType = res.headers.get('content-type');
if (cType) {
const {type} = parseContentType(cType);
if (type !== 'application/json' && type !== 'application/vnd.de.db.ris+json') {
if (type !== req.headers['Accept']) {
throw new HafasError('invalid/unsupported response content-type: ' + cType, null, errProps);
}
}
Expand Down
2 changes: 1 addition & 1 deletion p/db/journeys-req.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
to = profile.formatLocation(profile, to, 'to');
const filters = profile.formatProductsFilter({profile}, opt.products || {});
// TODO opt.accessibility

// TODO routingMode
let query = {
maxUmstiege: opt.transfers,
minUmstiegszeit: opt.transferTime,
Expand Down
10 changes: 10 additions & 0 deletions p/dbnav/base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"journeysEndpoint": "https://app.vendo.noncd.db.de/mob/angebote/fahrplan",
"refreshJourneysEndpointPrice": "https://app.vendo.noncd.db.de/mob/angebote/recon/autonomereservierung",
"refreshJourneysEndpointPolyline": "https://app.vendo.noncd.db.de/mob/trip/recon",
"locationsEndpoint": "https://app.vendo.noncd.db.de/mob/location/search",
"nearbyEndpoint": "https://app.vendo.noncd.db.de/mob/location/nearby",
"tripEndpoint": "https://app.vendo.noncd.db.de/mob/zuglauf",
"boardEndpoint": "https://app.vendo.noncd.db.de/mob/bahnhofstafel/abfahrt",
"defaultLanguage": "en"
}
11 changes: 11 additions & 0 deletions p/dbnav/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const getHeaders = (contentType) => {
return {
'X-Correlation-ID': 'null',
'Accept': contentType,
'Content-Type': contentType,
};
};

export {
getHeaders,
};
33 changes: 33 additions & 0 deletions p/dbnav/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {createRequire} from 'module';
const require = createRequire(import.meta.url);

const baseProfile = require('./base.json');
import {products} from '../../lib/products.js';
// import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
import {formatLocationFilter} from './location-filter.js';
import {formatLocationsReq} from './locations-req.js';
import {formatNearbyReq} from './nearby-req.js';
import {formatStationBoardReq} from './station-board-req.js';
// import {formatTravellers} from './travellers.js';
// import {parseTickets, parsePrice} from './tickets.js';

const profile = {
...baseProfile,
locale: 'de-DE',
timezone: 'Europe/Berlin',

products,
// formatJourneysReq,
// formatRefreshJourneyReq,
formatNearbyReq,
formatLocationsReq,
formatStationBoardReq,
formatLocationFilter,
// parsePrice,
// parseTickets,
// formatTravellers,
};

export {
profile,
};
20 changes: 20 additions & 0 deletions p/dbnav/location-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const formatLocationFilter = (stops, addresses, poi) => {
if (stops && addresses && poi) {
return ['ALL'];
}
const types = [];
if (stops) {
types.push('ST');
}
if (addresses) {
types.push('ADR');
}
if (poi) {
types.push('POI');
}
return types;
};

export {
formatLocationFilter,
};
20 changes: 20 additions & 0 deletions p/dbnav/locations-req.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {getHeaders} from './header.js';

const formatLocationsReq = (ctx, query) => {
const {profile, opt} = ctx;

return {
endpoint: profile.locationsEndpoint,
body: {
locationTypes: profile.formatLocationFilter(opt.stops, opt.addresses, opt.poi),
searchTerm: query,
maxResults: opt.results,
},
headers: getHeaders('application/x.db.vendo.mob.location.v3+json'),
method: 'post',
};
};

export {
formatLocationsReq,
};
29 changes: 29 additions & 0 deletions p/dbnav/nearby-req.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {getHeaders} from './header.js';

const formatNearbyReq = (ctx, location) => {
const {profile, opt} = ctx;
if (opt.distance > 10000) {
throw new Error('maximum supported distance by this endpoint is 10000');
}
// TODO location types
return {
endpoint: profile.nearbyEndpoint,
body: {
area: {
coordinates: {
longitude: location.longitude,
latitude: location.latitude,
},
radius: opt.distance || 10000,
},
maxResults: opt.results,
products: profile.formatProductsFilter(ctx, opt.products || {}, 'dbnav'),
},
headers: getHeaders('application/x.db.vendo.mob.location.v3+json'),
method: 'post',
};
};

export {
formatNearbyReq,
};
17 changes: 17 additions & 0 deletions p/dbnav/station-board-req.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {getHeaders} from './header.js';

const formatStationBoardReq = (ctx, station, type) => {
const {profile, opt} = ctx;

return {
endpoint: profile.boardEndpoint,
path: type == 'departures' ? 'abfahrt' : 'ankunft',
body: {anfragezeit: profile.formatTimeOfDay(profile, opt.when), datum: profile.formatDate(profile, opt.when), ursprungsBahnhofId: profile.formatStation(station).lid, verkehrsmittel: profile.formatProductsFilter(ctx, opt.products || {}, 'dbnav')},
method: 'POST',
header: getHeaders('application/x.db.vendo.mob.bahnhofstafeln.v2+json'),
};
};

export {
formatStationBoardReq,
};
10 changes: 5 additions & 5 deletions parse/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ const parseLocation = (ctx, l) => {
return null;
}

const lid = parse(l.id, {delimiter: '@'});
const lid = parse(l.id || l.locationId, {delimiter: '@'});
const res = {
type: 'location',
id: (l.extId || lid.L || l.evaNumber || l.evaNo || '').replace(leadingZeros, '') || null,
id: (l.extId || l.evaNr || lid.L || l.evaNumber || l.evaNo || '').replace(leadingZeros, '') || null,
};
const name = l.name || lid.O;

if (l.lat && l.lon) {
res.latitude = l.lat;
res.longitude = l.lon;
if (l.lat && l.lon || l.coordinates || l.position) {
res.latitude = l.lat || l.coordinates?.latitude || l.position?.latitude;
res.longitude = l.lon || l.coordinates?.longitude || l.position?.longitude;
} else if ('X' in lid && 'Y' in lid) {
res.latitude = lid.Y / 1000000;
res.longitude = lid.X / 1000000;
Expand Down
4 changes: 2 additions & 2 deletions parse/products.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const parseProducts = ({profile}, bitmask) => {
const parseProducts = ({profile}, products) => {
const res = {};
for (let product of profile.products) {
res[product.id] = Boolean(bitmask.find(p => p == product.vendo));
res[product.id] = Boolean(products.find(p => p == product.vendo || p == product.dbnav));
}
return res;
};
Expand Down
2 changes: 1 addition & 1 deletion test/format/db-journeys-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const opt = {
via: null,
stopovers: false,
transfers: null,
transferTime: 0,
transferTime: 0,
accessibility: 'none',
bike: false,
walkingSpeed: 'normal',
Expand Down

0 comments on commit debc1ee

Please sign in to comment.