From 01b36932718df668dd4277b891d84ecdbada7e13 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 18 Mar 2020 03:08:13 +0100 Subject: [PATCH 1/3] add SNCB/NMBS profile --- p/sncb/digicert-sha2-secure-server-ca.crt.pem | 49 +++++++++++++++ p/sncb/example.js | 41 +++++++++++++ p/sncb/index.js | 40 ++++++++++++ p/sncb/products.js | 61 +++++++++++++++++++ p/sncb/readme.md | 18 ++++++ readme.md | 1 + 6 files changed, 210 insertions(+) create mode 100644 p/sncb/digicert-sha2-secure-server-ca.crt.pem create mode 100644 p/sncb/example.js create mode 100644 p/sncb/index.js create mode 100644 p/sncb/products.js create mode 100644 p/sncb/readme.md diff --git a/p/sncb/digicert-sha2-secure-server-ca.crt.pem b/p/sncb/digicert-sha2-secure-server-ca.crt.pem new file mode 100644 index 00000000..23604862 --- /dev/null +++ b/p/sncb/digicert-sha2-secure-server-ca.crt.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg +U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83 +nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd +KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f +/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX +kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0 +/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C +AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6 +Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1 +oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD +QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v +d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh +xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB +CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl +5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA +8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC +2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit +c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0 +j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/p/sncb/example.js b/p/sncb/example.js new file mode 100644 index 00000000..6f5a23cc --- /dev/null +++ b/p/sncb/example.js @@ -0,0 +1,41 @@ +'use strict' + +const createClient = require('../..') +const sncbProfile = require('.') + +const client = createClient(sncbProfile, 'hafas-client-example') + +const gentStPieters = '8892007' +const bruxellesMidi = '8814001' +const gentPaddenhoek = { + type: 'location', + address: 'Gent, Paddenhoek', + latitude: 51.0517, longitude: 3.724878, +} + +// client.journeys(gentStPieters, bruxellesMidi, {stopovers: true, remarks: true}) +// .then(({journeys}) => { +// const leg = journeys[0].legs[0] +// return client.trip(leg.tripId, leg.line.name, {polyline: true}) +// }) +// .then(({journeys}) => { +// return client.refreshJourney(journeys[0].refreshToken, {remarks: true}) +// }) + +// client.departures(gentStPieters) +// client.arrivals(gentStPieters, {duration: 10, linesOfStops: true}) +// client.locations('gent') +// client.stop(gentStPieters, {linesOfStops: true}) +// client.nearby(gentPaddenhoek) +// client.radar({ +// north: 51.065, +// west: 3.688, +// south: 51.04, +// east: 3.748 +// }, {results: 10}) +// client.reachableFrom(gentPaddenhoek) + +.then((data) => { + console.log(require('util').inspect(data, {depth: null, colors: true})) +}) +.catch(console.error) diff --git a/p/sncb/index.js b/p/sncb/index.js new file mode 100644 index 00000000..ad7f6541 --- /dev/null +++ b/p/sncb/index.js @@ -0,0 +1,40 @@ +'use strict' + +const {readFileSync} = require('fs') +const {join} = require('path') +const {Agent} = require('https') +const products = require('./products') + +// `www.belgianrail.be:443` doesn't provide the necessary CA certificate +// chain for Node.js to trust the certificate, so we manually add it. +// todo: fix this properly, e.g. by letting them know +const ca = readFileSync(join(__dirname, 'digicert-sha2-secure-server-ca.crt.pem')) +const agent = new Agent({ca}) +const transformReq = (ctx, req) => ({...req, agent}) + +const transformReqBody = ({opt}, body) => { + body.client = {type: 'IPH', id: 'SNCB', name: 'sncb', v: '4030200'} + body.ver = '1.16' + body.auth = {type: 'AID', aid: 'sncb-mobi'} + body.lang = opt.language || 'fr' + + return body +} + +const sncbProfile = { + locale: 'fr-BE', + timezone: 'Europe/Brussels', + endpoint: 'https://www.belgianrail.be/jp/sncb-nmbs-routeplanner/mgate.exe', + + transformReq, + transformReqBody, + + products, + + trip: true, + refreshJourney: true, + radar: true, + // todo: `reachableFrom: true` fails with `H9240` +} + +module.exports = sncbProfile diff --git a/p/sncb/products.js b/p/sncb/products.js new file mode 100644 index 00000000..b82aeb31 --- /dev/null +++ b/p/sncb/products.js @@ -0,0 +1,61 @@ +'use strict' + +// https://www.belgiantrain.be/en/support/faq/faq-routes-schedules/faq-train-types +module.exports = [ // todo: 2, 8, 32, 128 + { + id: 'high-speed-train', + mode: 'train', + bitmasks: [1], + name: 'high-speed train', + short: 'HST', + default: true + }, + { + id: 'intercity-p', + mode: 'train', + bitmasks: [4], + name: 'InterCity/Peak', + short: 'IC/P', + default: true + }, + { + id: 's-train', + mode: 'train', + bitmasks: [16], + name: 'S-train', + short: 'S', + default: true + }, + { + id: 'local-train', + mode: 'train', + bitmasks: [64], + name: 'local train', + short: 'L', + default: true + }, + { + id: 'metro', + mode: 'train', + bitmasks: [256], + name: 'Metro', + short: 'M', + default: true + }, + { + id: 'bus', + mode: 'bus', + bitmasks: [512], + name: 'bus', + short: 'bus', + default: true + }, + { + id: 'tram', + mode: 'train', + bitmasks: [1024], + name: 'tram', + short: 'tram', + default: true + } +] diff --git a/p/sncb/readme.md b/p/sncb/readme.md new file mode 100644 index 00000000..e9503cde --- /dev/null +++ b/p/sncb/readme.md @@ -0,0 +1,18 @@ +# SNCB profile for `hafas-client` + +[*Société nationale des chemins de fer belges (SNCB)*/*Nationale Maatschappij der Belgische Spoorwegen (NMBS)*](https://en.wikipedia.org/wiki/National_Railway_Company_of_Belgium) is the major public transport provider of [Belgium](https://en.wikipedia.org/wiki/Belgium). This profile adds *SNCB*-specific customizations to `hafas-client`. + +## Usage + +```js +const createClient = require('hafas-client') +const sncbProfile = require('hafas-client/p/sncb') + +// create a client with SNCB profile +const client = createClient(sncbProfile, 'my-awesome-program') +``` + + +## Customisations + +- parses *SNCB*-specific products diff --git a/readme.md b/readme.md index bcc6d8e7..949405d8 100644 --- a/readme.md +++ b/readme.md @@ -207,6 +207,7 @@ HAFAS endpoint | wrapper library | docs | example code | source code ---------------|------------------|------|---------|------------ [Deutsche Bahn (DB)](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`db-hafas`](https://github.com/derhuerst/db-hafas) | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js) [Polskie Koleje Państwowe (PKP)](https://en.wikipedia.org/wiki/Polish_State_Railways) | [`pkp-hafas`](https://github.com/juliuste/pkp-hafas) | [docs](p/pkp/readme.md) | [example code](p/pkp/example.js) | [src](p/pkp/index.js) +[Belgian National Railways (SNCB/NMBS)](https://en.wikipedia.org/wiki/National_Railway_Company_of_Belgium) | - | [docs](p/sncb/readme.md) | [example code](p/sncb/example.js) | [src](p/sncb/index.js) [Berlin & Brandenburg public transport (VBB)](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas) | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js) [Berlin public transport (BVG)](https://en.wikipedia.org/wiki/Berliner_Verkehrsbetriebe) | [`bvg-hafas`](https://github.com/derhuerst/bvg-hafas) | [docs](p/bvg/readme.md) | [example code](p/bvg/example.js) | [src](p/bvg/index.js) [Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | [`oebb-hafas`](https://github.com/juliuste/oebb-hafas) | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js) From 17031f3e11007f138c5f4b43daf17cc3a3c7b303 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 18 Mar 2020 03:15:59 +0100 Subject: [PATCH 2/3] add SNCB/NMBS E2E tests :white_check_mark: --- test/e2e/index.js | 1 + test/e2e/sncb.js | 113 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 test/e2e/sncb.js diff --git a/test/e2e/index.js b/test/e2e/index.js index f4f8563c..106b5072 100644 --- a/test/e2e/index.js +++ b/test/e2e/index.js @@ -20,3 +20,4 @@ require('./rsag') require('./db-busradar-nrw') require('./invg') require('./pkp') +require('./sncb') diff --git a/test/e2e/sncb.js b/test/e2e/sncb.js new file mode 100644 index 00000000..9a25152d --- /dev/null +++ b/test/e2e/sncb.js @@ -0,0 +1,113 @@ +'use strict' + +const assert = require('assert') +const tapePromise = require('tape-promise').default +const tape = require('tape') + +const {createWhen} = require('./lib/util') +const createClient = require('../..') +const sncbProfile = require('../../p/sncb') +const products = require('../../p/sncb/products') +const createValidate = require('./lib/validate-fptf-with') +const testJourneysStationToStation = require('./lib/journeys-station-to-station') +const testRefreshJourney = require('./lib/refresh-journey') +const testArrivals = require('./lib/arrivals') +const testReachableFrom = require('./lib/reachable-from') + +const when = createWhen(sncbProfile.timezone, sncbProfile.locale) + +const cfg = { + when, + stationCoordsOptional: false, + products, + minLatitude: 46.513, + maxLatitude: 54.521, + minLongitude: -1.423, + maxLongitude: 15.26 +} + +const validate = createValidate(cfg) + +const test = tapePromise(tape) +const client = createClient(sncbProfile, 'public-transport/hafas-client:test') + +const gentStPieters = '8892007' +const bruxellesMidi = '8814001' +const gentPaddenhoek = { + type: 'location', + address: 'Gent, Paddenhoek', + latitude: 51.0517, longitude: 3.724878, +} + +test('journeys – Gent Sant Pieters to Bruxelles Midi', async (t) => { + const res = await client.journeys(gentStPieters, bruxellesMidi, { + results: 4, + departure: when, + stopovers: true + }) + + await testJourneysStationToStation({ + test: t, + res, + validate, + fromId: gentStPieters, + toId: bruxellesMidi + }) + t.end() +}) + +// todo: via works – with detour +// todo: without detour + +test('trip details', async (t) => { + const res = await client.journeys(gentStPieters, bruxellesMidi, { + results: 1, departure: when + }) + + const p = res.journeys[0].legs[0] + t.ok(p.tripId, 'precondition failed') + t.ok(p.line.name, 'precondition failed') + const trip = await client.trip(p.tripId, p.line.name, {when}) + + validate(t, trip, 'trip', 'trip') + t.end() +}) + +test('arrivals at Bruxelles Midi', async (t) => { + const arrivals = await client.arrivals(bruxellesMidi, { + duration: 10, when + }) + + validate(t, arrivals, 'arrivals', 'arrivals') + t.ok(arrivals.length > 0, 'must be >0 arrivals') + t.deepEqual(arrivals, arrivals.sort((a, b) => t.when > b.when)) + t.end() +}) + +// todo: nearby + +test('radar', async (t) => { + const vehicles = await client.radar({ + north: 51.065, + west: 3.688, + south: 51.04, + east: 3.748 + }, { + duration: 5 * 60, when, results: 10 + }) + + validate(t, vehicles, 'movements', 'vehicles') + t.end() +}) + +test.skip('reachableFrom', async (t) => { + await testReachableFrom({ + test: t, + reachableFrom: client.reachableFrom, + address: gentPaddenhoek, + when, + maxDuration: 15, + validate + }) + t.end() +}) From 7d3107e6a732daf5d3913f5b1be43114ac73a20d Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 19 Mar 2020 00:18:32 +0100 Subject: [PATCH 3/3] SNCB: normalize S-train line names --- p/sncb/index.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/p/sncb/index.js b/p/sncb/index.js index ad7f6541..b9937814 100644 --- a/p/sncb/index.js +++ b/p/sncb/index.js @@ -3,6 +3,9 @@ const {readFileSync} = require('fs') const {join} = require('path') const {Agent} = require('https') +const {strictEqual: eql} = require('assert') +const {parseHook} = require('../../lib/profile-hooks') +const parseLine = require('../../parse/line') const products = require('./products') // `www.belgianrail.be:443` doesn't provide the necessary CA certificate @@ -21,6 +24,35 @@ const transformReqBody = ({opt}, body) => { return body } +// todo: this is ugly +const lineNameWithoutFahrtNr = ({parsed}) => { + const {name, fahrtNr} = parsed + if (!name || !fahrtNr || !/s\d/i.test(name)) return parsed + const i = name.indexOf(fahrtNr) + if (i < 0) return parsed + + if ( + /\s/.test(name[i - 1] || '') && // space before + name.length === i + fahrtNr.length // nothing behind + ) return { + ...parsed, + name: name.slice(0, i - 1) + name.slice(i + fahrtNr.length + 1), + } + return parsed +} +eql(lineNameWithoutFahrtNr({ + parsed: {name: 'THA 123', fahrtNr: '123'} +}).name, 'THA 123') +eql(lineNameWithoutFahrtNr({ + parsed: {name: 'S1 123', fahrtNr: '123'} +}).name, 'S1') +eql(lineNameWithoutFahrtNr({ + parsed: {name: 'S1-123', fahrtNr: '123'} +}).name, 'S1-123') +eql(lineNameWithoutFahrtNr({ + parsed: {name: 'S1 123a', fahrtNr: '123'} +}).name, 'S1 123a') + const sncbProfile = { locale: 'fr-BE', timezone: 'Europe/Brussels', @@ -31,6 +63,8 @@ const sncbProfile = { products, + parseLine: parseHook(parseLine, lineNameWithoutFahrtNr), + trip: true, refreshJourney: true, radar: true,