mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-05-03 21:19:59 +03:00
dbnav profile: locations, nearby
This commit is contained in:
parent
ad6c356552
commit
debc1ee150
16 changed files with 167 additions and 34 deletions
1
api.js
1
api.js
|
@ -4,6 +4,7 @@ import {createHafasRestApi as createApi} from 'hafas-rest-api';
|
||||||
import {loyaltyCardParser} from 'db-rest/lib/loyalty-cards.js';
|
import {loyaltyCardParser} from 'db-rest/lib/loyalty-cards.js';
|
||||||
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js';
|
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js';
|
||||||
|
|
||||||
|
// TODO product support for nearby etc?
|
||||||
const mapRouteParsers = (route, parsers) => {
|
const mapRouteParsers = (route, parsers) => {
|
||||||
if (!route.includes('journey')) {
|
if (!route.includes('journey')) {
|
||||||
return parsers;
|
return parsers;
|
||||||
|
|
|
@ -34,6 +34,9 @@ const formatProductsFilter = (ctx, filter, key = 'vendo') => {
|
||||||
if (!foundDeselected && key == 'ris') {
|
if (!foundDeselected && key == 'ris') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
if (!foundDeselected && key == 'dbnav') {
|
||||||
|
return ['ALL'];
|
||||||
|
}
|
||||||
|
|
||||||
return products;
|
return products;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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?
|
TODO separate RIS::Boards profile?
|
||||||
const formatRisStationBoardReq = (ctx, station, type) => {
|
const formatRisStationBoardReq = (ctx, station, type) => {
|
||||||
|
|
11
index.js
11
index.js
|
@ -104,7 +104,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
const {res} = await profile.request({profile, opt}, userAgent, req);
|
const {res} = await profile.request({profile, opt}, userAgent, req);
|
||||||
|
|
||||||
const ctx = {profile, opt, common, res};
|
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 {
|
return {
|
||||||
[resultsField]: results,
|
[resultsField]: results,
|
||||||
|
@ -189,7 +190,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
const req = profile.formatJourneysReq({profile, opt}, from, to, when, outFrwd, journeysRef);
|
const req = profile.formatJourneysReq({profile, opt}, from, to, when, outFrwd, journeysRef);
|
||||||
const {res} = await profile.request({profile, opt}, userAgent, req);
|
const {res} = await profile.request({profile, opt}, userAgent, req);
|
||||||
const ctx = {profile, opt, common, res};
|
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
|
const journeys = verbindungen
|
||||||
.map(j => profile.parseJourney(ctx, j));
|
.map(j => profile.parseJourney(ctx, j));
|
||||||
|
|
||||||
|
@ -246,7 +247,11 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
const {res} = await profile.request({profile, opt}, userAgent, req);
|
const {res} = await profile.request({profile, opt}, userAgent, req);
|
||||||
|
|
||||||
const ctx = {profile, opt, common, res};
|
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
|
const stop = async (stop, opt = {}) => { // TODO
|
||||||
|
|
|
@ -100,7 +100,7 @@ const request = async (ctx, userAgent, reqData) => {
|
||||||
const endpoint = reqData.endpoint;
|
const endpoint = reqData.endpoint;
|
||||||
delete reqData.endpoint;
|
delete reqData.endpoint;
|
||||||
const rawReqBody = profile.transformReqBody(ctx, reqData.body);
|
const rawReqBody = profile.transformReqBody(ctx, reqData.body);
|
||||||
// console.log(rawReqBody, JSON.stringify(rawReqBody.req.reisende));
|
|
||||||
const req = profile.transformReq(ctx, {
|
const req = profile.transformReq(ctx, {
|
||||||
agent: getAgent(),
|
agent: getAgent(),
|
||||||
method: reqData.method,
|
method: reqData.method,
|
||||||
|
@ -121,7 +121,10 @@ const request = async (ctx, userAgent, reqData) => {
|
||||||
query: reqData.query,
|
query: reqData.query,
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = endpoint + (reqData.path || '') + '?' + stringify(req.query, {arrayFormat: 'brackets', encodeValuesOnly: true});
|
const url = endpoint + (reqData.path || '');
|
||||||
|
if (query) {
|
||||||
|
url += '?' + stringify(req.query, {arrayFormat: 'brackets', encodeValuesOnly: true});
|
||||||
|
}
|
||||||
const reqId = randomBytes(3)
|
const reqId = randomBytes(3)
|
||||||
.toString('hex');
|
.toString('hex');
|
||||||
const fetchReq = new Request(url, req);
|
const fetchReq = new Request(url, req);
|
||||||
|
@ -147,7 +150,7 @@ const request = async (ctx, userAgent, reqData) => {
|
||||||
let cType = res.headers.get('content-type');
|
let cType = res.headers.get('content-type');
|
||||||
if (cType) {
|
if (cType) {
|
||||||
const {type} = parseContentType(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);
|
throw new HafasError('invalid/unsupported response content-type: ' + cType, null, errProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ const formatJourneysReq = (ctx, from, to, when, outFrwd, journeysRef) => {
|
||||||
to = profile.formatLocation(profile, to, 'to');
|
to = profile.formatLocation(profile, to, 'to');
|
||||||
const filters = profile.formatProductsFilter({profile}, opt.products || {});
|
const filters = profile.formatProductsFilter({profile}, opt.products || {});
|
||||||
// TODO opt.accessibility
|
// TODO opt.accessibility
|
||||||
|
// TODO routingMode
|
||||||
let query = {
|
let query = {
|
||||||
maxUmstiege: opt.transfers,
|
maxUmstiege: opt.transfers,
|
||||||
minUmstiegszeit: opt.transferTime,
|
minUmstiegszeit: opt.transferTime,
|
||||||
|
|
10
p/dbnav/base.json
Normal file
10
p/dbnav/base.json
Normal file
|
@ -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
p/dbnav/header.js
Normal file
11
p/dbnav/header.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const getHeaders = (contentType) => {
|
||||||
|
return {
|
||||||
|
'X-Correlation-ID': 'null',
|
||||||
|
'Accept': contentType,
|
||||||
|
'Content-Type': contentType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getHeaders,
|
||||||
|
};
|
33
p/dbnav/index.js
Normal file
33
p/dbnav/index.js
Normal file
|
@ -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
p/dbnav/location-filter.js
Normal file
20
p/dbnav/location-filter.js
Normal file
|
@ -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
p/dbnav/locations-req.js
Normal file
20
p/dbnav/locations-req.js
Normal file
|
@ -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
p/dbnav/nearby-req.js
Normal file
29
p/dbnav/nearby-req.js
Normal file
|
@ -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
p/dbnav/station-board-req.js
Normal file
17
p/dbnav/station-board-req.js
Normal file
|
@ -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,
|
||||||
|
};
|
|
@ -13,16 +13,16 @@ const parseLocation = (ctx, l) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lid = parse(l.id, {delimiter: '@'});
|
const lid = parse(l.id || l.locationId, {delimiter: '@'});
|
||||||
const res = {
|
const res = {
|
||||||
type: 'location',
|
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;
|
const name = l.name || lid.O;
|
||||||
|
|
||||||
if (l.lat && l.lon) {
|
if (l.lat && l.lon || l.coordinates || l.position) {
|
||||||
res.latitude = l.lat;
|
res.latitude = l.lat || l.coordinates?.latitude || l.position?.latitude;
|
||||||
res.longitude = l.lon;
|
res.longitude = l.lon || l.coordinates?.longitude || l.position?.longitude;
|
||||||
} else if ('X' in lid && 'Y' in lid) {
|
} else if ('X' in lid && 'Y' in lid) {
|
||||||
res.latitude = lid.Y / 1000000;
|
res.latitude = lid.Y / 1000000;
|
||||||
res.longitude = lid.X / 1000000;
|
res.longitude = lid.X / 1000000;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const parseProducts = ({profile}, bitmask) => {
|
const parseProducts = ({profile}, products) => {
|
||||||
const res = {};
|
const res = {};
|
||||||
for (let product of profile.products) {
|
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;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue