mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-06-19 10:42:33 +03:00
Compare commits
No commits in common. "5e502a6f442b42905bdda9ece70bd7ff43fa1b8e" and "932a4aa0dbf1e3d52799ce1d90b76c4157646332" have entirely different histories.
5e502a6f44
...
932a4aa0db
36 changed files with 1365 additions and 1911 deletions
54
.eslintrc.json
Normal file
54
.eslintrc.json
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:@stylistic/all-extends"],
|
||||
"ignorePatterns": ["node_modules", "*example.js"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"curly": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"ignoreRestSiblings": false
|
||||
}
|
||||
],
|
||||
"@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-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"]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"test/**"
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
29
.github/workflows/test.yml
vendored
29
.github/workflows/test.yml
vendored
|
@ -6,20 +6,6 @@ env:
|
|||
npm_config_cache: /tmp/npm-cache
|
||||
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
@ -39,12 +25,13 @@ jobs:
|
|||
|
||||
- id: cache-npm
|
||||
name: restore npm cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: npm-cache-${{ github.ref_name }}-${{ matrix.node-version }}-unit-tests
|
||||
key: npm-cache-${{ github.ref_name }}
|
||||
path: ${{ env.npm_config_cache }}
|
||||
- run: npm install
|
||||
|
||||
- run: npm run lint
|
||||
- run: npm run test-unit
|
||||
|
||||
integration-tests:
|
||||
|
@ -66,9 +53,9 @@ jobs:
|
|||
|
||||
- id: cache-npm
|
||||
name: restore npm cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: npm-cache-${{ github.ref_name }}-${{ matrix.node-version }}-integration-tests
|
||||
key: npm-cache-${{ github.ref_name }}
|
||||
path: ${{ env.npm_config_cache }}
|
||||
- run: npm install
|
||||
|
||||
|
@ -79,7 +66,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
node-version: [16.x]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
@ -90,9 +77,9 @@ jobs:
|
|||
|
||||
- id: cache-npm
|
||||
name: restore npm cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: npm-cache-${{ github.ref_name }}-${{ matrix.node-version }}-e2e-tests
|
||||
key: npm-cache-${{ github.ref_name }}
|
||||
path: ${{ env.npm_config_cache }}
|
||||
- run: npm install
|
||||
|
||||
|
|
34
api.js
34
api.js
|
@ -1,9 +1,32 @@
|
|||
import {createClient} from './index.js';
|
||||
import {profile as dbProfile} from './p/db/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 {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 = {
|
||||
hostname: process.env.HOSTNAME || 'localhost',
|
||||
|
@ -22,15 +45,10 @@ const config = {
|
|||
mapRouteParsers,
|
||||
};
|
||||
|
||||
const profiles = {
|
||||
db: dbProfile,
|
||||
dbnav: dbnavProfile,
|
||||
dbweb: dbwebProfile,
|
||||
};
|
||||
|
||||
const start = async () => {
|
||||
const vendo = createClient(
|
||||
profiles[process.env.DB_PROFILE] || dbnavProfile,
|
||||
process.env.DB_PROFILE == 'db' ? dbProfile : dbnavProfile,
|
||||
process.env.USER_AGENT || 'link-to-your-project-or-email',
|
||||
config,
|
||||
);
|
||||
|
|
7
debug.js
7
debug.js
|
@ -1,7 +0,0 @@
|
|||
import {createClient} from './index.js';
|
||||
import {profile} from './p/dbnav/index.js';
|
||||
|
||||
const client = createClient(profile, 'hafas-client-debug');
|
||||
|
||||
const journeys = await client.journeys('8000105', '8000261', {results: 1});
|
||||
console.log(journeys);
|
|
@ -24,7 +24,7 @@ With `opt`, you can override the default options, which look like this:
|
|||
```js
|
||||
{
|
||||
when: new Date(),
|
||||
direction: null, // only supported in `dbweb` and with `enrichStations=true` (experimental)
|
||||
direction: null, // not supported
|
||||
line: null, // not supported
|
||||
duration: 10, // show departures for the next n minutes
|
||||
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
|
||||
// departures at related stations
|
||||
// e.g. those that belong together on the metro map.
|
||||
includeRelatedStations: true,
|
||||
includeRelatedStations: true, // only true supported
|
||||
language: 'en' // language to get results in
|
||||
}
|
||||
```
|
||||
|
|
|
@ -385,14 +385,13 @@ paths:
|
|||
- bahncard-2nd-25
|
||||
- bahncard-1st-50
|
||||
- bahncard-2nd-50
|
||||
- bahncard-1st-100
|
||||
- bahncard-2nd-100
|
||||
- vorteilscard
|
||||
- halbtaxabo-railplus
|
||||
- halbtaxabo
|
||||
- generalabonnement-1st
|
||||
- generalabonnement-2nd
|
||||
- nl-40
|
||||
- at-klimaticket
|
||||
- voordeelurenabo-railplus
|
||||
- voordeelurenabo
|
||||
- shcard
|
||||
- generalabonnement
|
||||
- name: firstClass
|
||||
in: query
|
||||
description: Search for first-class options?
|
||||
|
@ -2088,7 +2087,7 @@ components:
|
|||
type: string
|
||||
format: date-time
|
||||
direction:
|
||||
description: only show departures heading to this station, only supported for `dbweb` profile
|
||||
description: only show departures heading to this station
|
||||
default: undefined
|
||||
type: string
|
||||
line:
|
||||
|
@ -2125,7 +2124,7 @@ components:
|
|||
type: boolean
|
||||
includeRelatedStations:
|
||||
description: departures at related stations
|
||||
default: true
|
||||
default: false
|
||||
type: boolean
|
||||
products:
|
||||
$ref: '#/components/schemas/Products'
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
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,10 +6,9 @@ const c = {
|
|||
VOORDEELURENABO: Symbol('Voordeelurenabo'),
|
||||
SHCARD: Symbol('SH-Card'),
|
||||
GENERALABONNEMENT: Symbol('General-Abonnement'),
|
||||
NL_40: Symbol('NL-40%'),
|
||||
AT_KLIMATICKET: Symbol('AT-KlimaTicket'),
|
||||
};
|
||||
|
||||
// see https://gist.github.com/juliuste/202bb04f450a79f8fa12a2ec3abcd72d
|
||||
const formatLoyaltyCard = (data) => {
|
||||
if (!data) {
|
||||
return {
|
||||
|
@ -20,7 +19,7 @@ const formatLoyaltyCard = (data) => {
|
|||
const cls = data.class === 1 ? 'KLASSE_1' : 'KLASSE_2';
|
||||
if (data.type.toString() === c.BAHNCARD.toString()) {
|
||||
return {
|
||||
art: 'BAHNCARD' + (data.business ? 'BUSINESS' : '') + data.discount,
|
||||
art: 'BAHNCARD' + data.discount,
|
||||
klasse: cls,
|
||||
};
|
||||
}
|
||||
|
@ -36,24 +35,13 @@ const formatLoyaltyCard = (data) => {
|
|||
klasse: 'KLASSENLOS',
|
||||
};
|
||||
}
|
||||
// TODO Rest
|
||||
if (data.type.toString() === c.GENERALABONNEMENT.toString()) {
|
||||
return {
|
||||
art: 'CH-GENERAL-ABONNEMENT',
|
||||
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 {
|
||||
art: 'KEINE_ERMAESSIGUNG',
|
||||
klasse: 'KLASSENLOS',
|
||||
|
|
48
index.js
48
index.js
|
@ -26,10 +26,10 @@ const validateLocation = (loc, name = 'location') => {
|
|||
}
|
||||
};
|
||||
|
||||
const loadEnrichedStationData = (profile) => new Promise((resolve, reject) => {
|
||||
import('db-hafas-stations').then(m => {
|
||||
const loadEnrichedStationData = (profile) => new Promise(async (resolve, reject) => {
|
||||
const { default: readStations} = await import('db-hafas-stations');
|
||||
const items = {};
|
||||
m.default.full()
|
||||
readStations.full()
|
||||
.on('data', (station) => {
|
||||
items[station.id] = station;
|
||||
items[station.name] = station;
|
||||
|
@ -43,26 +43,17 @@ const loadEnrichedStationData = (profile) => new Promise((resolve, reject) => {
|
|||
.once('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 = {}) => {
|
||||
profile = Object.assign({}, defaultProfile, profile);
|
||||
validateProfile(profile);
|
||||
const common = {};
|
||||
let shouldLoadEnrichedStationData = false;
|
||||
if (typeof opt.enrichStations === 'function') {
|
||||
profile.enrichStation = opt.enrichStations;
|
||||
} else if (opt.enrichStations !== false) {
|
||||
shouldLoadEnrichedStationData = true;
|
||||
if (opt.enrichStations !== false) {
|
||||
loadEnrichedStationData(profile)
|
||||
.then(locations => {
|
||||
common.locations = locations;
|
||||
});
|
||||
}
|
||||
|
||||
if ('string' !== typeof userAgent) {
|
||||
|
@ -73,7 +64,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
}
|
||||
|
||||
const _stationBoard = async (station, type, resultsField, parse, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
if (isObj(station) && station.id) {
|
||||
station = station.id;
|
||||
} else if ('string' !== typeof station) {
|
||||
|
@ -116,15 +106,9 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
const {res} = await profile.request({profile, opt}, userAgent, req);
|
||||
|
||||
const ctx = {profile, opt, common, res};
|
||||
let results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries)
|
||||
const results = (res[resultsField] || res.items || res.bahnhofstafelAbfahrtPositionen || res.bahnhofstafelAnkunftPositionen || res.entries)
|
||||
.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 {
|
||||
[resultsField]: results,
|
||||
realtimeDataUpdatedAt: null, // TODO
|
||||
|
@ -139,7 +123,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
};
|
||||
|
||||
const journeys = async (from, to, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
if ('earlierThan' in opt && 'laterThan' in opt) {
|
||||
throw new TypeError('opt.earlierThan and opt.laterThan are mutually exclusive.');
|
||||
}
|
||||
|
@ -222,8 +205,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
};
|
||||
|
||||
const refreshJourney = async (refreshToken, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
|
||||
if ('string' !== typeof refreshToken || !refreshToken) {
|
||||
throw new TypeError('refreshToken must be a non-empty string.');
|
||||
}
|
||||
|
@ -250,8 +231,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
};
|
||||
|
||||
const locations = async (query, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
|
||||
if (!isNonEmptyString(query)) {
|
||||
throw new TypeError('query must be a non-empty string.');
|
||||
}
|
||||
|
@ -278,8 +257,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
};
|
||||
|
||||
const stop = async (stop, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
|
||||
if (isObj(stop) && stop.id) {
|
||||
stop = stop.id;
|
||||
} else if ('string' !== typeof stop) {
|
||||
|
@ -301,8 +278,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
};
|
||||
|
||||
const nearby = async (location, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
|
||||
validateLocation(location, 'location');
|
||||
|
||||
opt = Object.assign({
|
||||
|
@ -333,8 +308,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
};
|
||||
|
||||
const trip = async (id, opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
|
||||
if (!isNonEmptyString(id)) {
|
||||
throw new TypeError('id must be a non-empty string.');
|
||||
}
|
||||
|
@ -362,8 +335,6 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
|
||||
// todo [breaking]: rename to trips()?
|
||||
const tripsByName = async (_lineNameOrFahrtNr = '*', _opt = {}) => {
|
||||
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
|
||||
|
||||
throw new Error('not implemented');
|
||||
};
|
||||
|
||||
|
@ -390,5 +361,4 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
|
||||
export {
|
||||
createClient,
|
||||
loadEnrichedStationData,
|
||||
};
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
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 {parseJourney} from '../parse/journey.js';
|
||||
import {parseLine} from '../parse/line.js';
|
||||
import {parseLocation, enrichStation} from '../parse/location.js';
|
||||
import {parseLocation} from '../parse/location.js';
|
||||
import {parsePolyline} from '../parse/polyline.js';
|
||||
import {parseOperator} from '../parse/operator.js';
|
||||
import {parseRemarks, parseCancelled} from '../parse/remarks.js';
|
||||
|
@ -37,7 +37,7 @@ import {formatTravellers} from '../format/travellers.js';
|
|||
import {formatLoyaltyCard} from '../format/loyalty-cards.js';
|
||||
import {formatTransfers} from '../format/transfers.js';
|
||||
|
||||
const DEBUG = (/(^|,)hafas-client(,|$)/).test(typeof process !== 'undefined' ? process.env.DEBUG || '' : '');
|
||||
const DEBUG = (/(^|,)hafas-client(,|$)/).test((typeof process !== 'undefined') ? (process.env.DEBUG || '') : '');
|
||||
const logRequest = DEBUG
|
||||
? (_, req, reqId) => console.error(String(req.body))
|
||||
: () => { };
|
||||
|
@ -82,7 +82,6 @@ const defaultProfile = {
|
|||
parseLine,
|
||||
parseStationName: id,
|
||||
parseLocation,
|
||||
enrichStation,
|
||||
parsePolyline,
|
||||
parseOperator,
|
||||
parseRemarks,
|
||||
|
|
|
@ -7,10 +7,9 @@ const randomBytesHex = (nBytes = 8) => {
|
|||
const array = new Uint8Array(nBytes);
|
||||
crypto.getRandomValues(array);
|
||||
return Array.from(array)
|
||||
.map((byte) => byte.toString(16)
|
||||
.padStart(2, '0'))
|
||||
.map((byte) => byte.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
};
|
||||
};
|
||||
|
||||
const checkIfResponseIsOk = (_) => {
|
||||
const {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
# ISC License
|
||||
|
||||
- Copyright © 2024 Jannis R
|
||||
- Copyright © 2025 traines-source
|
||||
Copyright (c) 2024, Jannis R
|
||||
|
||||
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.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import dbnavBase from '../dbnav/base.json' with { type: 'json' };
|
||||
import dbregioguideBase from '../dbregioguide/base.json' with { type: 'json' };
|
||||
import dbnavBase from '../dbnav/base.json' with { type: "json" };
|
||||
import dbregioguideBase from '../dbregioguide/base.json' with { type: "json" };
|
||||
import {products} from '../../lib/products.js';
|
||||
|
||||
// journeys()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import baseProfile from './base.json' with { type: 'json' };
|
||||
import baseProfile from './base.json' with { type: "json" };
|
||||
import {products} from '../../lib/products.js';
|
||||
import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
|
||||
import {formatTripReq} from './trip-req.js';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import baseProfile from './base.json' with { type: 'json' };
|
||||
import baseProfile from './base.json' with { type: "json" };
|
||||
import {products} from '../../lib/products.js';
|
||||
import {formatTripReq} from './trip-req.js';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import baseProfile from './base.json' with { type: 'json' };
|
||||
import baseProfile from './base.json' with { type: "json" };
|
||||
import {products} from '../../lib/products.js';
|
||||
import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
|
||||
import {formatLocationFilter} from './location-filter.js';
|
||||
|
|
|
@ -8,7 +8,7 @@ const formatStationBoardReq = (ctx, station, type) => {
|
|||
ortExtId: station,
|
||||
zeit: profile.formatTimeOfDay(profile, opt.when),
|
||||
datum: profile.formatDate(profile, opt.when),
|
||||
mitVias: opt.stopovers || Boolean(opt.direction) || undefined,
|
||||
mitVias: opt.stopovers || undefined,
|
||||
verkehrsmittel: profile.formatProductsFilter(ctx, opt.products || {}),
|
||||
},
|
||||
method: 'GET',
|
||||
|
|
2732
package-lock.json
generated
2732
package-lock.json
generated
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "db-vendo-client",
|
||||
"description": "Client for bahn.de public transport APIs.",
|
||||
"version": "6.5.0",
|
||||
"version": "6.4.0",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
|
@ -68,15 +68,12 @@
|
|||
"uuid": "^11.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.20.0",
|
||||
"@pollyjs/adapter-node-http": "^6.0.5",
|
||||
"@pollyjs/core": "^6.0.5",
|
||||
"@pollyjs/persister-fs": "^6.0.5",
|
||||
"@stylistic/eslint-plugin": "^3.1.0",
|
||||
"@stylistic/eslint-plugin": "^1.5.1",
|
||||
"db-rest": "github:derhuerst/db-rest",
|
||||
"eslint": "^9.20.1",
|
||||
"globals": "^15.15.0",
|
||||
"eslint": "^8.56.0",
|
||||
"hafas-rest-api": "^5.1.3",
|
||||
"is-coordinates": "^2.0.2",
|
||||
"is-roughly-equal": "^0.1.0",
|
||||
|
@ -86,8 +83,8 @@
|
|||
"validate-fptf": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"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:record": "VCR_MODE=record tap -t60 -j1 test/e2e/*.js",
|
||||
|
|
|
@ -40,7 +40,7 @@ const createParseArrOrDep = (prefix) => {
|
|||
res.remarks = profile.parseRemarks(ctx, d);
|
||||
}
|
||||
|
||||
if ((opt.stopovers || opt.direction) && Array.isArray(d.ueber)) {
|
||||
if (opt.stopovers && Array.isArray(d.ueber)) {
|
||||
const stopovers = d.ueber
|
||||
.map(viaName => profile.parseStopover(ctx, {name: viaName}, null));
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const parseLocation = (ctx, l) => {
|
|||
}
|
||||
|
||||
const lid = parse(l.id || l.locationId, {delimiter: '@'});
|
||||
let res = {
|
||||
const res = {
|
||||
type: 'location',
|
||||
id: (l.extId || l.evaNr || lid.L || l.evaNumber || l.evaNo || l.bahnhofsId || '').replace(leadingZeros, '') || null,
|
||||
};
|
||||
|
@ -46,7 +46,13 @@ const parseLocation = (ctx, l) => {
|
|||
stop.products = profile.parseProducts(ctx, l.products);
|
||||
}
|
||||
|
||||
stop = profile.enrichStation(ctx, stop);
|
||||
if (common && common.locations && common.locations[stop.id]) {
|
||||
delete stop.type;
|
||||
stop = {
|
||||
...common.locations[stop.id],
|
||||
...stop,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO isMeta
|
||||
// TODO entrances, lines
|
||||
|
@ -64,8 +70,6 @@ const parseLocation = (ctx, l) => {
|
|||
}
|
||||
|
||||
res.name = name;
|
||||
res = enrichStation(ctx, res);
|
||||
|
||||
if (l.type === ADDRESS || lid.A == '2') {
|
||||
res.address = name;
|
||||
}
|
||||
|
@ -76,22 +80,6 @@ const parseLocation = (ctx, l) => {
|
|||
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 {
|
||||
parseLocation,
|
||||
enrichStation,
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbnav/index.js';
|
||||
import res from './fixtures/dbnav-departures.json' with { type: 'json' };
|
||||
import res from './fixtures/dbnav-departures.json' with { type: "json" };
|
||||
import {dbnavDepartures as expected} from './fixtures/dbnav-departures.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -5,7 +5,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbnav/index.js';
|
||||
import res from './fixtures/dbnav-refresh-journey.json' with { type: 'json' };
|
||||
import res from './fixtures/dbnav-refresh-journey.json' with { type: "json" };
|
||||
import {dbNavJourney as expected} from './fixtures/dbnav-refresh-journey.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -5,7 +5,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbnav/index.js';
|
||||
import res from './fixtures/dbnav-stop.json' with { type: 'json' };
|
||||
import res from './fixtures/dbnav-stop.json' with { type: "json" };
|
||||
import {dbnavDepartures as expected} from './fixtures/dbnav-stop.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -4,7 +4,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbnav/index.js';
|
||||
import res from './fixtures/dbnav-trip.json' with { type: 'json' };
|
||||
import res from './fixtures/dbnav-trip.json' with { type: "json" };
|
||||
import {dbTrip as expected} from './fixtures/dbnav-trip.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -4,7 +4,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbregioguide/index.js';
|
||||
import res from './fixtures/dbregioguide-trip.json' with { type: 'json' };
|
||||
import res from './fixtures/dbregioguide-trip.json' with { type: "json" };
|
||||
import {dbTrip as expected} from './fixtures/dbregioguide-trip.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -4,7 +4,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbweb/index.js';
|
||||
import res from './fixtures/dbris-arrivals.json' with { type: 'json' };
|
||||
import res from './fixtures/dbris-arrivals.json' with { type: "json" };
|
||||
import {dbArrivals as expected} from './fixtures/dbris-arrivals.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -4,10 +4,10 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
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';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: true});
|
||||
const {profile} = client;
|
||||
|
||||
const opt = {
|
||||
|
|
|
@ -4,7 +4,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbweb/index.js';
|
||||
import res from './fixtures/dbweb-journey.json' with { type: 'json' };
|
||||
import res from './fixtures/dbweb-journey.json' with { type: "json" };
|
||||
import {dbwebJourney as expected} from './fixtures/dbweb-journey.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -4,7 +4,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbweb/index.js';
|
||||
import res from './fixtures/dbweb-refresh-journey.json' with { type: 'json' };
|
||||
import res from './fixtures/dbweb-refresh-journey.json' with { type: "json" };
|
||||
import {dbJourney as expected} from './fixtures/dbweb-refresh-journey.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -4,7 +4,7 @@ import tap from 'tap';
|
|||
|
||||
import {createClient} from '../index.js';
|
||||
import {profile as rawProfile} from '../p/dbweb/index.js';
|
||||
import res from './fixtures/dbweb-trip.json' with { type: 'json' };
|
||||
import res from './fixtures/dbweb-trip.json' with { type: "json" };
|
||||
import {dbwebTrip as expected} from './fixtures/dbweb-trip.js';
|
||||
|
||||
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
|
||||
|
|
|
@ -395,10 +395,19 @@ tap.test('trip details', async (t) => {
|
|||
});
|
||||
|
||||
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||
const res = await new Promise((resolve) => {
|
||||
let interval = setInterval(async () => { // repeat evaluating `departures()` until stations are enriched
|
||||
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({
|
||||
test: t,
|
||||
res,
|
||||
|
@ -409,6 +418,8 @@ tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
|||
});
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const res = await new Promise((resolve) => {
|
||||
let interval = setInterval(async () => { // repeat evaluating `departures()` until stations are enriched
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: jungfernheide,
|
||||
|
@ -420,15 +431,31 @@ tap.test('departures with station object', async (t) => {
|
|||
},
|
||||
}, {when});
|
||||
|
||||
if (res.departures[0].stop.name !== undefined) { // ctx.common.locations have loaded
|
||||
clearInterval(interval);
|
||||
return resolve(res);
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
validate(t, res, 'departuresResponse', 'res');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
||||
const res = await new Promise((resolve) => {
|
||||
let interval = setInterval(async () => { // repeat evaluating `arrivals()` until stations are enriched
|
||||
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({
|
||||
test: t,
|
||||
res,
|
||||
|
|
|
@ -4,7 +4,6 @@ import {parseProducts} from '../../parse/products.js';
|
|||
|
||||
const profile = {
|
||||
parseLocation: parse,
|
||||
enrichStation: (ctx, stop) => stop,
|
||||
parseStationName: (_, name) => name.toLowerCase(),
|
||||
parseProducts,
|
||||
products: [{
|
||||
|
|
Loading…
Add table
Reference in a new issue