mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-23 15:19:35 +02:00
Merge branch 'main' into main
This commit is contained in:
commit
406d24a051
20 changed files with 355 additions and 165 deletions
29
.github/workflows/test.yml
vendored
29
.github/workflows/test.yml
vendored
|
@ -6,6 +6,20 @@ env:
|
||||||
npm_config_cache: /tmp/npm-cache
|
npm_config_cache: /tmp/npm-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: "Use Node.js"
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
- run: npm install
|
||||||
|
|
||||||
|
- run: npm run lint
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -25,13 +39,12 @@ jobs:
|
||||||
|
|
||||||
- id: cache-npm
|
- id: cache-npm
|
||||||
name: restore npm cache
|
name: restore npm cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
key: npm-cache-${{ github.ref_name }}
|
key: npm-cache-${{ github.ref_name }}-${{ matrix.node-version }}-unit-tests
|
||||||
path: ${{ env.npm_config_cache }}
|
path: ${{ env.npm_config_cache }}
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
- run: npm run lint
|
|
||||||
- run: npm run test-unit
|
- run: npm run test-unit
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
|
@ -53,9 +66,9 @@ jobs:
|
||||||
|
|
||||||
- id: cache-npm
|
- id: cache-npm
|
||||||
name: restore npm cache
|
name: restore npm cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
key: npm-cache-${{ github.ref_name }}
|
key: npm-cache-${{ github.ref_name }}-${{ matrix.node-version }}-integration-tests
|
||||||
path: ${{ env.npm_config_cache }}
|
path: ${{ env.npm_config_cache }}
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
|
@ -66,7 +79,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16.x]
|
node-version: [18.x]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -77,9 +90,9 @@ jobs:
|
||||||
|
|
||||||
- id: cache-npm
|
- id: cache-npm
|
||||||
name: restore npm cache
|
name: restore npm cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
key: npm-cache-${{ github.ref_name }}
|
key: npm-cache-${{ github.ref_name }}-${{ matrix.node-version }}-e2e-tests
|
||||||
path: ${{ env.npm_config_cache }}
|
path: ${{ env.npm_config_cache }}
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
Copyright (c) 2024, Jannis R
|
# ISC License
|
||||||
|
|
||||||
|
- Copyright © 2024 Jannis R
|
||||||
|
- Copyright © 2025 traines-source
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
34
api.js
34
api.js
|
@ -1,32 +1,9 @@
|
||||||
import {createClient} from './index.js';
|
import {createClient} from './index.js';
|
||||||
import {profile as dbProfile} from './p/db/index.js';
|
import {profile as dbProfile} from './p/db/index.js';
|
||||||
import {profile as dbnavProfile} from './p/dbnav/index.js';
|
import {profile as dbnavProfile} from './p/dbnav/index.js';
|
||||||
|
import {profile as dbwebProfile} from './p/dbweb/index.js';
|
||||||
|
import {mapRouteParsers} from './lib/api-parsers.js';
|
||||||
import {createHafasRestApi as createApi} from 'hafas-rest-api';
|
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;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...parsers,
|
|
||||||
loyaltyCard: loyaltyCardParser,
|
|
||||||
firstClass: {
|
|
||||||
description: 'Search for first-class options?',
|
|
||||||
type: 'boolean',
|
|
||||||
default: 'false',
|
|
||||||
parse: parseBoolean,
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
description: 'Age of traveller',
|
|
||||||
type: 'integer',
|
|
||||||
defaultStr: '*adult*',
|
|
||||||
parse: parseInteger,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
hostname: process.env.HOSTNAME || 'localhost',
|
hostname: process.env.HOSTNAME || 'localhost',
|
||||||
|
@ -45,10 +22,15 @@ const config = {
|
||||||
mapRouteParsers,
|
mapRouteParsers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const profiles = {
|
||||||
|
db: dbProfile,
|
||||||
|
dbnav: dbnavProfile,
|
||||||
|
dbweb: dbwebProfile,
|
||||||
|
};
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
const vendo = createClient(
|
const vendo = createClient(
|
||||||
process.env.DB_PROFILE == 'db' ? dbProfile : dbnavProfile,
|
profiles[process.env.DB_PROFILE] || dbnavProfile,
|
||||||
process.env.USER_AGENT || 'link-to-your-project-or-email',
|
process.env.USER_AGENT || 'link-to-your-project-or-email',
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,7 +24,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
when: new Date(),
|
when: new Date(),
|
||||||
direction: null, // not supported
|
direction: null, // only supported in `dbweb` and with `enrichStations=true` (experimental)
|
||||||
line: null, // not supported
|
line: null, // not supported
|
||||||
duration: 10, // show departures for the next n minutes
|
duration: 10, // show departures for the next n minutes
|
||||||
results: null, // max. number of results; `null` means "whatever HAFAS wants"
|
results: null, // max. number of results; `null` means "whatever HAFAS wants"
|
||||||
|
@ -35,7 +35,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
stopovers: false, // fetch & parse previous/next stopovers?, only supported with `dbweb` profile
|
stopovers: false, // fetch & parse previous/next stopovers?, only supported with `dbweb` profile
|
||||||
// departures at related stations
|
// departures at related stations
|
||||||
// e.g. those that belong together on the metro map.
|
// e.g. those that belong together on the metro map.
|
||||||
includeRelatedStations: true, // only true supported
|
includeRelatedStations: true,
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -385,13 +385,14 @@ paths:
|
||||||
- bahncard-2nd-25
|
- bahncard-2nd-25
|
||||||
- bahncard-1st-50
|
- bahncard-1st-50
|
||||||
- bahncard-2nd-50
|
- bahncard-2nd-50
|
||||||
|
- bahncard-1st-100
|
||||||
|
- bahncard-2nd-100
|
||||||
- vorteilscard
|
- vorteilscard
|
||||||
- halbtaxabo-railplus
|
|
||||||
- halbtaxabo
|
- halbtaxabo
|
||||||
- voordeelurenabo-railplus
|
- generalabonnement-1st
|
||||||
- voordeelurenabo
|
- generalabonnement-2nd
|
||||||
- shcard
|
- nl-40
|
||||||
- generalabonnement
|
- at-klimaticket
|
||||||
- name: firstClass
|
- name: firstClass
|
||||||
in: query
|
in: query
|
||||||
description: Search for first-class options?
|
description: Search for first-class options?
|
||||||
|
@ -2087,7 +2088,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
direction:
|
direction:
|
||||||
description: only show departures heading to this station
|
description: only show departures heading to this station, only supported for `dbweb` profile
|
||||||
default: undefined
|
default: undefined
|
||||||
type: string
|
type: string
|
||||||
line:
|
line:
|
||||||
|
@ -2124,7 +2125,7 @@ components:
|
||||||
type: boolean
|
type: boolean
|
||||||
includeRelatedStations:
|
includeRelatedStations:
|
||||||
description: departures at related stations
|
description: departures at related stations
|
||||||
default: false
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
products:
|
products:
|
||||||
$ref: '#/components/schemas/Products'
|
$ref: '#/components/schemas/Products'
|
||||||
|
|
61
eslint.config.js
Normal file
61
eslint.config.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import eslintPluginJs from '@eslint/js';
|
||||||
|
import eslintPluginStylistic from '@stylistic/eslint-plugin';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
|
||||||
|
const config = [
|
||||||
|
eslintPluginJs.configs.recommended,
|
||||||
|
eslintPluginStylistic.configs['all-flat'],
|
||||||
|
{
|
||||||
|
files: ['**/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@stylistic/array-bracket-newline': ['error', 'consistent'],
|
||||||
|
'@stylistic/array-element-newline': ['error', 'consistent'],
|
||||||
|
'@stylistic/arrow-parens': 'off',
|
||||||
|
'@stylistic/comma-dangle': ['error', 'always-multiline'],
|
||||||
|
'@stylistic/dot-location': ['error', 'property'],
|
||||||
|
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
|
||||||
|
'@stylistic/function-paren-newline': 'off',
|
||||||
|
'@stylistic/indent': ['error', 'tab'],
|
||||||
|
'@stylistic/indent-binary-ops': ['error', 'tab'],
|
||||||
|
'@stylistic/max-len': 'off',
|
||||||
|
'@stylistic/multiline-comment-style': 'off',
|
||||||
|
'@stylistic/multiline-ternary': ['error', 'always-multiline'],
|
||||||
|
'@stylistic/newline-per-chained-call': ['error', {ignoreChainWithDepth: 1}],
|
||||||
|
'@stylistic/no-mixed-operators': 'off',
|
||||||
|
'@stylistic/no-tabs': 'off',
|
||||||
|
'@stylistic/object-property-newline': 'off',
|
||||||
|
'@stylistic/one-var-declaration-per-line': 'off',
|
||||||
|
'@stylistic/operator-linebreak': ['error', 'before'],
|
||||||
|
'@stylistic/padded-blocks': 'off',
|
||||||
|
'@stylistic/quote-props': ['error', 'consistent-as-needed'],
|
||||||
|
'@stylistic/quotes': ['error', 'single'],
|
||||||
|
'curly': 'error',
|
||||||
|
'no-implicit-coercion': 'error',
|
||||||
|
'no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
vars: 'all',
|
||||||
|
args: 'none',
|
||||||
|
ignoreRestSiblings: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**', '**/example.js'],
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@stylistic/semi': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default config;
|
|
@ -6,9 +6,10 @@ const c = {
|
||||||
VOORDEELURENABO: Symbol('Voordeelurenabo'),
|
VOORDEELURENABO: Symbol('Voordeelurenabo'),
|
||||||
SHCARD: Symbol('SH-Card'),
|
SHCARD: Symbol('SH-Card'),
|
||||||
GENERALABONNEMENT: Symbol('General-Abonnement'),
|
GENERALABONNEMENT: Symbol('General-Abonnement'),
|
||||||
|
NL_40: Symbol('NL-40%'),
|
||||||
|
AT_KLIMATICKET: Symbol('AT-KlimaTicket'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// see https://gist.github.com/juliuste/202bb04f450a79f8fa12a2ec3abcd72d
|
|
||||||
const formatLoyaltyCard = (data) => {
|
const formatLoyaltyCard = (data) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return {
|
return {
|
||||||
|
@ -19,7 +20,7 @@ const formatLoyaltyCard = (data) => {
|
||||||
const cls = data.class === 1 ? 'KLASSE_1' : 'KLASSE_2';
|
const cls = data.class === 1 ? 'KLASSE_1' : 'KLASSE_2';
|
||||||
if (data.type.toString() === c.BAHNCARD.toString()) {
|
if (data.type.toString() === c.BAHNCARD.toString()) {
|
||||||
return {
|
return {
|
||||||
art: 'BAHNCARD' + data.discount,
|
art: 'BAHNCARD' + (data.business ? 'BUSINESS' : '') + data.discount,
|
||||||
klasse: cls,
|
klasse: cls,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -35,13 +36,24 @@ const formatLoyaltyCard = (data) => {
|
||||||
klasse: 'KLASSENLOS',
|
klasse: 'KLASSENLOS',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// TODO Rest
|
|
||||||
if (data.type.toString() === c.GENERALABONNEMENT.toString()) {
|
if (data.type.toString() === c.GENERALABONNEMENT.toString()) {
|
||||||
return {
|
return {
|
||||||
art: 'CH-GENERAL-ABONNEMENT',
|
art: 'CH-GENERAL-ABONNEMENT',
|
||||||
klasse: cls,
|
klasse: cls,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (data.type.toString() === c.NL_40.toString()) {
|
||||||
|
return {
|
||||||
|
art: 'NL-40_OHNE_RAILPLUS',
|
||||||
|
klasse: 'KLASSENLOS',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (data.type.toString() === c.AT_KLIMATICKET.toString()) {
|
||||||
|
return {
|
||||||
|
art: 'KLIMATICKET_OE',
|
||||||
|
klasse: 'KLASSENLOS',
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
art: 'KEINE_ERMAESSIGUNG',
|
art: 'KEINE_ERMAESSIGUNG',
|
||||||
klasse: 'KLASSENLOS',
|
klasse: 'KLASSENLOS',
|
||||||
|
|
41
index.js
41
index.js
|
@ -46,15 +46,23 @@ const loadEnrichedStationData = (profile) => new Promise((resolve, reject) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const applyEnrichedStationData = async (ctx, shouldLoadEnrichedStationData) => {
|
||||||
|
const {profile, common} = ctx;
|
||||||
|
if (shouldLoadEnrichedStationData && !common.locations) {
|
||||||
|
const locations = await loadEnrichedStationData(profile);
|
||||||
|
common.locations = locations;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const createClient = (profile, userAgent, opt = {}) => {
|
const createClient = (profile, userAgent, opt = {}) => {
|
||||||
profile = Object.assign({}, defaultProfile, profile);
|
profile = Object.assign({}, defaultProfile, profile);
|
||||||
validateProfile(profile);
|
validateProfile(profile);
|
||||||
const common = {};
|
const common = {};
|
||||||
if (opt.enrichStations !== false) {
|
let shouldLoadEnrichedStationData = false;
|
||||||
loadEnrichedStationData(profile)
|
if (typeof opt.enrichStations === 'function') {
|
||||||
.then(locations => {
|
profile.enrichStation = opt.enrichStations;
|
||||||
common.locations = locations;
|
} else if (opt.enrichStations !== false) {
|
||||||
});
|
shouldLoadEnrichedStationData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('string' !== typeof userAgent) {
|
if ('string' !== typeof userAgent) {
|
||||||
|
@ -65,6 +73,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const _stationBoard = async (station, type, resultsField, parse, opt = {}) => {
|
const _stationBoard = async (station, type, resultsField, parse, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
if (isObj(station) && station.id) {
|
if (isObj(station) && station.id) {
|
||||||
station = station.id;
|
station = station.id;
|
||||||
} else if ('string' !== typeof station) {
|
} else if ('string' !== typeof station) {
|
||||||
|
@ -107,9 +116,15 @@ 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 || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries)
|
let results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries)
|
||||||
.map(res => parse(ctx, res)); // TODO sort?, slice
|
.map(res => parse(ctx, res)); // TODO sort?, slice
|
||||||
|
|
||||||
|
if (!opt.includeRelatedStations) {
|
||||||
|
results = results.filter(r => !r.stop?.id || r.stop.id == station);
|
||||||
|
}
|
||||||
|
if (opt.direction) {
|
||||||
|
results = results.filter(r => !r.nextStopovers || r.nextStopovers.find(s => s.stop?.id == opt.direction || s.stop?.name == opt.direction));
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
[resultsField]: results,
|
[resultsField]: results,
|
||||||
realtimeDataUpdatedAt: null, // TODO
|
realtimeDataUpdatedAt: null, // TODO
|
||||||
|
@ -124,6 +139,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const journeys = async (from, to, opt = {}) => {
|
const journeys = async (from, to, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
if ('earlierThan' in opt && 'laterThan' in opt) {
|
if ('earlierThan' in opt && 'laterThan' in opt) {
|
||||||
throw new TypeError('opt.earlierThan and opt.laterThan are mutually exclusive.');
|
throw new TypeError('opt.earlierThan and opt.laterThan are mutually exclusive.');
|
||||||
}
|
}
|
||||||
|
@ -206,6 +222,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshJourney = async (refreshToken, opt = {}) => {
|
const refreshJourney = async (refreshToken, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
|
|
||||||
if ('string' !== typeof refreshToken || !refreshToken) {
|
if ('string' !== typeof refreshToken || !refreshToken) {
|
||||||
throw new TypeError('refreshToken must be a non-empty string.');
|
throw new TypeError('refreshToken must be a non-empty string.');
|
||||||
}
|
}
|
||||||
|
@ -232,6 +250,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const locations = async (query, opt = {}) => {
|
const locations = async (query, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
|
|
||||||
if (!isNonEmptyString(query)) {
|
if (!isNonEmptyString(query)) {
|
||||||
throw new TypeError('query must be a non-empty string.');
|
throw new TypeError('query must be a non-empty string.');
|
||||||
}
|
}
|
||||||
|
@ -258,6 +278,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = async (stop, opt = {}) => {
|
const stop = async (stop, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
|
|
||||||
if (isObj(stop) && stop.id) {
|
if (isObj(stop) && stop.id) {
|
||||||
stop = stop.id;
|
stop = stop.id;
|
||||||
} else if ('string' !== typeof stop) {
|
} else if ('string' !== typeof stop) {
|
||||||
|
@ -279,6 +301,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const nearby = async (location, opt = {}) => {
|
const nearby = async (location, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
|
|
||||||
validateLocation(location, 'location');
|
validateLocation(location, 'location');
|
||||||
|
|
||||||
opt = Object.assign({
|
opt = Object.assign({
|
||||||
|
@ -309,6 +333,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const trip = async (id, opt = {}) => {
|
const trip = async (id, opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
|
|
||||||
if (!isNonEmptyString(id)) {
|
if (!isNonEmptyString(id)) {
|
||||||
throw new TypeError('id must be a non-empty string.');
|
throw new TypeError('id must be a non-empty string.');
|
||||||
}
|
}
|
||||||
|
@ -336,6 +362,8 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
|
|
||||||
// todo [breaking]: rename to trips()?
|
// todo [breaking]: rename to trips()?
|
||||||
const tripsByName = async (_lineNameOrFahrtNr = '*', _opt = {}) => {
|
const tripsByName = async (_lineNameOrFahrtNr = '*', _opt = {}) => {
|
||||||
|
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||||
|
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -362,4 +390,5 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createClient,
|
createClient,
|
||||||
|
loadEnrichedStationData,
|
||||||
};
|
};
|
||||||
|
|
74
lib/api-parsers.js
Normal file
74
lib/api-parsers.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import {data as cards} from '../format/loyalty-cards.js';
|
||||||
|
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js';
|
||||||
|
|
||||||
|
const typesByName = new Map([
|
||||||
|
['bahncard-1st-25', {type: cards.BAHNCARD, discount: 25, class: 1}],
|
||||||
|
['bahncard-2nd-25', {type: cards.BAHNCARD, discount: 25, class: 2}],
|
||||||
|
['bahncard-1st-50', {type: cards.BAHNCARD, discount: 50, class: 1}],
|
||||||
|
['bahncard-2nd-50', {type: cards.BAHNCARD, discount: 50, class: 2}],
|
||||||
|
['bahncard-1st-100', {type: cards.BAHNCARD, discount: 100, class: 1}],
|
||||||
|
['bahncard-2nd-100', {type: cards.BAHNCARD, discount: 100, class: 2}],
|
||||||
|
['vorteilscard', {type: cards.VORTEILSCARD}],
|
||||||
|
['halbtaxabo-railplus', {type: cards.HALBTAXABO}],
|
||||||
|
['halbtaxabo', {type: cards.HALBTAXABO}],
|
||||||
|
['voordeelurenabo-railplus', {type: cards.VOORDEELURENABO}],
|
||||||
|
['voordeelurenabo', {type: cards.VOORDEELURENABO}],
|
||||||
|
['shcard', {type: cards.SHCARD}],
|
||||||
|
['generalabonnement-1st', {type: cards.GENERALABONNEMENT, class: 1}],
|
||||||
|
['generalabonnement-2nd', {type: cards.GENERALABONNEMENT, class: 2}],
|
||||||
|
['generalabonnement', {type: cards.GENERALABONNEMENT}],
|
||||||
|
['nl-40', {type: cards.NL_40}],
|
||||||
|
['at-klimaticket', {type: cards.AT_KLIMATICKET}],
|
||||||
|
]);
|
||||||
|
const types = Array.from(typesByName.keys());
|
||||||
|
|
||||||
|
const parseLoyaltyCard = (key, val) => {
|
||||||
|
if (typesByName.has(val)) {
|
||||||
|
return typesByName.get(val);
|
||||||
|
}
|
||||||
|
if (!val) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new Error(key + ' must be one of ' + types.join(', '));
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseArrayOr = (parseEntry) => {
|
||||||
|
return (key, val) => {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return val.map(e => parseEntry(key, e));
|
||||||
|
}
|
||||||
|
return parseEntry(key, val);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapRouteParsers = (route, parsers) => {
|
||||||
|
if (route !== 'journeys') {
|
||||||
|
return parsers;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...parsers,
|
||||||
|
firstClass: {
|
||||||
|
description: 'Search for first-class options?',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
parse: parseBoolean,
|
||||||
|
},
|
||||||
|
loyaltyCard: {
|
||||||
|
description: 'Type of loyalty card in use.',
|
||||||
|
type: 'string',
|
||||||
|
enum: types,
|
||||||
|
defaultStr: '*none*',
|
||||||
|
parse: parseArrayOr(parseLoyaltyCard),
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
description: 'Age of traveller',
|
||||||
|
type: 'integer',
|
||||||
|
defaultStr: '*adult*',
|
||||||
|
parse: parseArrayOr(parseInteger),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
mapRouteParsers,
|
||||||
|
};
|
|
@ -16,7 +16,7 @@ import {parseTrip} from '../parse/trip.js';
|
||||||
import {parseJourneyLeg} from '../parse/journey-leg.js';
|
import {parseJourneyLeg} from '../parse/journey-leg.js';
|
||||||
import {parseJourney} from '../parse/journey.js';
|
import {parseJourney} from '../parse/journey.js';
|
||||||
import {parseLine} from '../parse/line.js';
|
import {parseLine} from '../parse/line.js';
|
||||||
import {parseLocation} from '../parse/location.js';
|
import {parseLocation, enrichStation} from '../parse/location.js';
|
||||||
import {parsePolyline} from '../parse/polyline.js';
|
import {parsePolyline} from '../parse/polyline.js';
|
||||||
import {parseOperator} from '../parse/operator.js';
|
import {parseOperator} from '../parse/operator.js';
|
||||||
import {parseRemarks, parseCancelled} from '../parse/remarks.js';
|
import {parseRemarks, parseCancelled} from '../parse/remarks.js';
|
||||||
|
@ -82,6 +82,7 @@ const defaultProfile = {
|
||||||
parseLine,
|
parseLine,
|
||||||
parseStationName: id,
|
parseStationName: id,
|
||||||
parseLocation,
|
parseLocation,
|
||||||
|
enrichStation,
|
||||||
parsePolyline,
|
parsePolyline,
|
||||||
parseOperator,
|
parseOperator,
|
||||||
parseRemarks,
|
parseRemarks,
|
||||||
|
|
|
@ -8,7 +8,7 @@ const formatStationBoardReq = (ctx, station, type) => {
|
||||||
ortExtId: station,
|
ortExtId: station,
|
||||||
zeit: profile.formatTimeOfDay(profile, opt.when),
|
zeit: profile.formatTimeOfDay(profile, opt.when),
|
||||||
datum: profile.formatDate(profile, opt.when),
|
datum: profile.formatDate(profile, opt.when),
|
||||||
mitVias: opt.stopovers || undefined,
|
mitVias: opt.stopovers || Boolean(opt.direction) || undefined,
|
||||||
verkehrsmittel: profile.formatProductsFilter(ctx, opt.products || {}),
|
verkehrsmittel: profile.formatProductsFilter(ctx, opt.products || {}),
|
||||||
},
|
},
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
116
package-lock.json
generated
116
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "db-vendo-client",
|
"name": "db-vendo-client",
|
||||||
"version": "6.4.0",
|
"version": "6.5.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "db-vendo-client",
|
"name": "db-vendo-client",
|
||||||
"version": "6.4.0",
|
"version": "6.5.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"content-type": "^1.0.4",
|
"content-type": "^1.0.4",
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
"@pollyjs/adapter-node-http": "^6.0.5",
|
"@pollyjs/adapter-node-http": "^6.0.5",
|
||||||
"@pollyjs/core": "^6.0.5",
|
"@pollyjs/core": "^6.0.5",
|
||||||
"@pollyjs/persister-fs": "^6.0.5",
|
"@pollyjs/persister-fs": "^6.0.5",
|
||||||
"@stylistic/eslint-plugin": "^1.5.1",
|
"@stylistic/eslint-plugin": "^3.1.0",
|
||||||
"db-rest": "github:derhuerst/db-rest",
|
"db-rest": "github:derhuerst/db-rest",
|
||||||
"eslint": "^9.20.1",
|
"eslint": "^9.20.1",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
@ -151,6 +151,19 @@
|
||||||
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint-community/regexpp": {
|
"node_modules/@eslint-community/regexpp": {
|
||||||
"version": "4.12.1",
|
"version": "4.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
|
||||||
|
@ -976,8 +989,9 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stylistic/eslint-plugin-js": "^1.8.1",
|
"@typescript-eslint/utils": "^8.13.0",
|
||||||
"@types/eslint": "^8.56.10",
|
"eslint-visitor-keys": "^4.2.0",
|
||||||
|
"espree": "^10.3.0",
|
||||||
"estraverse": "^5.3.0",
|
"estraverse": "^5.3.0",
|
||||||
"picomatch": "^4.0.2"
|
"picomatch": "^4.0.2"
|
||||||
},
|
},
|
||||||
|
@ -1782,17 +1796,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "6.21.0",
|
"version": "8.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz",
|
||||||
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
|
"integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "6.21.0",
|
"@typescript-eslint/types": "8.24.0",
|
||||||
"@typescript-eslint/visitor-keys": "6.21.0"
|
"@typescript-eslint/visitor-keys": "8.24.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -1800,13 +1814,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "6.21.0",
|
"version": "8.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz",
|
||||||
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
|
"integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -1814,32 +1828,46 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "6.21.0",
|
"version": "8.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz",
|
||||||
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
|
"integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "6.21.0",
|
"@typescript-eslint/types": "8.24.0",
|
||||||
"@typescript-eslint/visitor-keys": "6.21.0",
|
"@typescript-eslint/visitor-keys": "8.24.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"minimatch": "9.0.3",
|
"minimatch": "^9.0.4",
|
||||||
"semver": "^7.5.4",
|
"semver": "^7.6.0",
|
||||||
"ts-api-utils": "^1.0.1"
|
"ts-api-utils": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependencies": {
|
||||||
"typescript": {
|
"typescript": ">=4.8.4 <5.8.0"
|
||||||
"optional": true
|
}
|
||||||
}
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
|
@ -1869,17 +1897,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "6.21.0",
|
"version": "8.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz",
|
||||||
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
|
"integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "6.21.0",
|
"@typescript-eslint/types": "8.24.0",
|
||||||
"eslint-visitor-keys": "^3.4.1"
|
"eslint-visitor-keys": "^4.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -3393,13 +3421,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-visitor-keys": {
|
"node_modules/eslint-visitor-keys": {
|
||||||
"version": "3.4.3",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
||||||
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
|
@ -8015,16 +8043,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "1.4.3",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
|
||||||
"integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
|
"integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=18.12"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">=4.2.0"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tshy": {
|
"node_modules/tshy": {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "db-vendo-client",
|
"name": "db-vendo-client",
|
||||||
"description": "Client for bahn.de public transport APIs.",
|
"description": "Client for bahn.de public transport APIs.",
|
||||||
"version": "6.4.0",
|
"version": "6.5.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
"@pollyjs/adapter-node-http": "^6.0.5",
|
"@pollyjs/adapter-node-http": "^6.0.5",
|
||||||
"@pollyjs/core": "^6.0.5",
|
"@pollyjs/core": "^6.0.5",
|
||||||
"@pollyjs/persister-fs": "^6.0.5",
|
"@pollyjs/persister-fs": "^6.0.5",
|
||||||
"@stylistic/eslint-plugin": "^1.5.1",
|
"@stylistic/eslint-plugin": "^3.1.0",
|
||||||
"db-rest": "github:derhuerst/db-rest",
|
"db-rest": "github:derhuerst/db-rest",
|
||||||
"eslint": "^9.20.1",
|
"eslint": "^9.20.1",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
@ -86,8 +86,8 @@
|
||||||
"validate-fptf": "^3.0.0"
|
"validate-fptf": "^3.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint --fix",
|
||||||
"test-unit": "tap test/lib/*.js test/*.js test/format/*.js test/parse/*.js",
|
"test-unit": "tap test/lib/*.js test/*.js test/format/*.js test/parse/*.js",
|
||||||
"test-integration": "VCR_MODE=playback tap test/e2e/*.js",
|
"test-integration": "VCR_MODE=playback tap test/e2e/*.js",
|
||||||
"test-integration:record": "VCR_MODE=record tap -t60 -j1 test/e2e/*.js",
|
"test-integration:record": "VCR_MODE=record tap -t60 -j1 test/e2e/*.js",
|
||||||
|
|
|
@ -40,7 +40,7 @@ const createParseArrOrDep = (prefix) => {
|
||||||
res.remarks = profile.parseRemarks(ctx, d);
|
res.remarks = profile.parseRemarks(ctx, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opt.stopovers && Array.isArray(d.ueber)) {
|
if ((opt.stopovers || opt.direction) && Array.isArray(d.ueber)) {
|
||||||
const stopovers = d.ueber
|
const stopovers = d.ueber
|
||||||
.map(viaName => profile.parseStopover(ctx, {name: viaName}, null));
|
.map(viaName => profile.parseStopover(ctx, {name: viaName}, null));
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,11 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
|
||||||
const stops = pt.halte?.length && pt.halte || pt.stops?.length && pt.stops || [];
|
const stops = pt.halte?.length && pt.halte || pt.stops?.length && pt.stops || [];
|
||||||
const res = {
|
const res = {
|
||||||
origin: stops.length && profile.parseLocation(ctx, stops[0].ort || stops[0].station || stops[0])
|
origin: stops.length && profile.parseLocation(ctx, stops[0].ort || stops[0].station || stops[0])
|
||||||
|| pt.abgangsOrt?.name && profile.parseLocation(ctx, pt.abgangsOrt)
|
|| pt.abgangsOrt?.name && profile.parseLocation(ctx, pt.abgangsOrt)
|
||||||
|| locationFallback(pt.abfahrtsOrtExtId, pt.abfahrtsOrt, fallbackLocations),
|
|| locationFallback(pt.abfahrtsOrtExtId, pt.abfahrtsOrt, fallbackLocations),
|
||||||
destination: stops.length && profile.parseLocation(ctx, stops[stops.length - 1].ort || stops[stops.length - 1].station || stops[stops.length - 1])
|
destination: stops.length && profile.parseLocation(ctx, stops[stops.length - 1].ort || stops[stops.length - 1].station || stops[stops.length - 1])
|
||||||
|| pt.ankunftsOrt?.name && profile.parseLocation(ctx, pt.ankunftsOrt)
|
|| pt.ankunftsOrt?.name && profile.parseLocation(ctx, pt.ankunftsOrt)
|
||||||
|| locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt, fallbackLocations),
|
|| locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt, fallbackLocations),
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelledDep = stops.length && profile.parseCancelled(stops[0]);
|
const cancelledDep = stops.length && profile.parseCancelled(stops[0]);
|
||||||
|
|
|
@ -14,7 +14,7 @@ const parseLocation = (ctx, l) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const lid = parse(l.id || l.locationId, {delimiter: '@'});
|
const lid = parse(l.id || l.locationId, {delimiter: '@'});
|
||||||
const res = {
|
let res = {
|
||||||
type: 'location',
|
type: 'location',
|
||||||
id: (l.extId || l.evaNr || lid.L || l.evaNumber || l.evaNo || l.bahnhofsId || '').replace(leadingZeros, '') || null,
|
id: (l.extId || l.evaNr || lid.L || l.evaNumber || l.evaNo || l.bahnhofsId || '').replace(leadingZeros, '') || null,
|
||||||
};
|
};
|
||||||
|
@ -46,13 +46,7 @@ const parseLocation = (ctx, l) => {
|
||||||
stop.products = profile.parseProducts(ctx, l.products);
|
stop.products = profile.parseProducts(ctx, l.products);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (common && common.locations && common.locations[stop.id]) {
|
stop = profile.enrichStation(ctx, stop);
|
||||||
delete stop.type;
|
|
||||||
stop = {
|
|
||||||
...common.locations[stop.id],
|
|
||||||
...stop,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO isMeta
|
// TODO isMeta
|
||||||
// TODO entrances, lines
|
// TODO entrances, lines
|
||||||
|
@ -70,6 +64,8 @@ const parseLocation = (ctx, l) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
res.name = name;
|
res.name = name;
|
||||||
|
res = enrichStation(ctx, res);
|
||||||
|
|
||||||
if (l.type === ADDRESS || lid.A == '2') {
|
if (l.type === ADDRESS || lid.A == '2') {
|
||||||
res.address = name;
|
res.address = name;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +76,22 @@ const parseLocation = (ctx, l) => {
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const enrichStation = (ctx, stop, locations) => {
|
||||||
|
const {common} = ctx;
|
||||||
|
const locs = locations || common?.locations;
|
||||||
|
const rich = locs && (locs[stop.id] || locs[stop.name]);
|
||||||
|
if (rich) {
|
||||||
|
delete stop.type;
|
||||||
|
delete stop.id;
|
||||||
|
stop = {
|
||||||
|
...rich,
|
||||||
|
...stop,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return stop;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
parseLocation,
|
parseLocation,
|
||||||
|
enrichStation,
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,11 +37,11 @@ const parseRemarks = (ctx, ref) => {
|
||||||
let res = {
|
let res = {
|
||||||
code: remark.code || remark.key || remark.id,
|
code: remark.code || remark.key || remark.id,
|
||||||
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || remark.shortText
|
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || remark.shortText
|
||||||
|| Object.values(remark.descriptions || {})
|
|| Object.values(remark.descriptions || {})
|
||||||
.shift()?.textShort,
|
.shift()?.textShort,
|
||||||
text: remark.nachrichtLang || remark.value || remark.text || remark.caption
|
text: remark.nachrichtLang || remark.value || remark.text || remark.caption
|
||||||
|| Object.values(remark.descriptions || {})
|
|| Object.values(remark.descriptions || {})
|
||||||
.shift()?.text,
|
.shift()?.text,
|
||||||
type: type,
|
type: type,
|
||||||
};
|
};
|
||||||
if (remark.modDateTime || remark.letzteAktualisierung) {
|
if (remark.modDateTime || remark.letzteAktualisierung) {
|
||||||
|
@ -208,9 +208,9 @@ const parseCancelled = (ref) => {
|
||||||
|| ref.journeyCancelled
|
|| ref.journeyCancelled
|
||||||
|| (ref.risNotizen || ref.echtzeitNotizen || ref.meldungen) && Boolean(
|
|| (ref.risNotizen || ref.echtzeitNotizen || ref.meldungen) && Boolean(
|
||||||
(ref.risNotizen || ref.echtzeitNotizen || ref.meldungen).find(r => r.key == 'text.realtime.stop.cancelled'
|
(ref.risNotizen || ref.echtzeitNotizen || ref.meldungen).find(r => r.key == 'text.realtime.stop.cancelled'
|
||||||
|| r.type == 'HALT_AUSFALL'
|
|| r.type == 'HALT_AUSFALL'
|
||||||
|| r.text == 'Halt entfällt'
|
|| r.text == 'Halt entfällt'
|
||||||
|| r.text == 'Stop cancelled',
|
|| r.text == 'Stop cancelled',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {profile as rawProfile} from '../p/dbweb/index.js';
|
||||||
import res from './fixtures/dbweb-departures.json' with { type: 'json' };
|
import res from './fixtures/dbweb-departures.json' with { type: 'json' };
|
||||||
import {dbwebDepartures as expected} from './fixtures/dbweb-departures.js';
|
import {dbwebDepartures as expected} from './fixtures/dbweb-departures.js';
|
||||||
|
|
||||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: true});
|
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||||
const {profile} = client;
|
const {profile} = client;
|
||||||
|
|
||||||
const opt = {
|
const opt = {
|
||||||
|
|
|
@ -395,17 +395,8 @@ tap.test('trip details', async (t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||||
const res = await new Promise((resolve) => {
|
const res = await client.departures(blnSchwedterStr, {
|
||||||
let interval = setInterval(async () => { // repeat evaluating `departures()` until stations are enriched
|
duration: 5, when,
|
||||||
const res = await client.departures(blnSchwedterStr, {
|
|
||||||
duration: 5, when,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.departures[0].stop.name !== undefined) { // ctx.common.locations have loaded
|
|
||||||
clearInterval(interval);
|
|
||||||
return resolve(res);
|
|
||||||
}
|
|
||||||
}, 4000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await testDepartures({
|
await testDepartures({
|
||||||
|
@ -418,42 +409,24 @@ tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('departures with station object', async (t) => {
|
tap.test('departures with station object', async (t) => {
|
||||||
const res = await new Promise((resolve) => {
|
const res = await client.departures({
|
||||||
let interval = setInterval(async () => { // repeat evaluating `departures()` until stations are enriched
|
type: 'station',
|
||||||
const res = await client.departures({
|
id: jungfernheide,
|
||||||
type: 'station',
|
name: 'Berlin Jungfernheide',
|
||||||
id: jungfernheide,
|
location: {
|
||||||
name: 'Berlin Jungfernheide',
|
type: 'location',
|
||||||
location: {
|
latitude: 1.23,
|
||||||
type: 'location',
|
longitude: 2.34,
|
||||||
latitude: 1.23,
|
},
|
||||||
longitude: 2.34,
|
}, {when});
|
||||||
},
|
|
||||||
}, {when});
|
|
||||||
|
|
||||||
if (res.departures[0].stop.name !== undefined) { // ctx.common.locations have loaded
|
|
||||||
clearInterval(interval);
|
|
||||||
return resolve(res);
|
|
||||||
}
|
|
||||||
}, 4000);
|
|
||||||
});
|
|
||||||
|
|
||||||
validate(t, res, 'departuresResponse', 'res');
|
validate(t, res, 'departuresResponse', 'res');
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
||||||
const res = await new Promise((resolve) => {
|
const res = await client.arrivals(blnSchwedterStr, {
|
||||||
let interval = setInterval(async () => { // repeat evaluating `arrivals()` until stations are enriched
|
duration: 5, when,
|
||||||
const res = await client.arrivals(blnSchwedterStr, {
|
|
||||||
duration: 5, when,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.arrivals[0].stop.name !== undefined) { // ctx.common.locations have loaded
|
|
||||||
clearInterval(interval);
|
|
||||||
return resolve(res);
|
|
||||||
}
|
|
||||||
}, 4000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await testArrivals({
|
await testArrivals({
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {parseProducts} from '../../parse/products.js';
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
parseLocation: parse,
|
parseLocation: parse,
|
||||||
|
enrichStation: (ctx, stop) => stop,
|
||||||
parseStationName: (_, name) => name.toLowerCase(),
|
parseStationName: (_, name) => name.toLowerCase(),
|
||||||
parseProducts,
|
parseProducts,
|
||||||
products: [{
|
products: [{
|
||||||
|
|
Loading…
Add table
Reference in a new issue