Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

comboios

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

comboios - npm Package Compare versions

Comparing version 0.0.5 to 0.1.0

lib/helpers.js

96

lib/fetch.js

@@ -10,14 +10,14 @@ 'use strict'

const getNewToken = () =>
got.post("https://api.cp.pt/cp-api/oauth/token", {
headers: {
'Accept': 'application/json',
'Authorization': 'Basic Y3AtbW9iaWxlOnBhc3M=', // Base64 of "cp-mobile:pass"
'Content-Type': 'application/x-www-form-urlencoded'
},
body: stringify({
grant_type: 'client_credentials'
})
})
.then((res) => JSON.parse(res.body))
.then((res) => res.access_token)
got.post('https://api.cp.pt/cp-api/oauth/token', {
headers: {
'Accept': 'application/json',
'Authorization': 'Basic Y3AtbW9iaWxlOnBhc3M=', // Base64 of "cp-mobile:pass"
'Content-Type': 'application/x-www-form-urlencoded'
},
body: stringify({
grant_type: 'client_credentials'
})
})
.then((res) => JSON.parse(res.body))
.then((res) => res.access_token)

@@ -27,50 +27,46 @@ const savedToken = () => token ? Promise.resolve(token) : renewSavedToken()

const renewSavedToken = () =>
getNewToken()
.then((res) => {
token = res
return res
})
getNewToken()
.then((res) => {
token = res
return res
})
const getRequest = (url, params = {}) => async (token) => {
const res = await got.get(url, {
json: true,
query: params,
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
})
return res.body
const res = await got.get(url, {
json: true,
query: params,
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
})
return res.body
}
const get = (url, params) =>
retry(() => savedToken()
.then(getRequest(url, params))
.catch((error) => renewSavedToken), // todo: handling non-token-specific errors
{retries: 3}
)
const postRequest = (url, body = {}) => async (token) => {
const res = await got.post(url, {
body: JSON.stringify(body),
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
return JSON.parse(res.body)
const res = await got.post(url, {
body: JSON.stringify(body),
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
return JSON.parse(res.body)
}
const post = (url, body) =>
retry(() => savedToken()
.then(postRequest(url, body))
.catch((error) => renewSavedToken), // todo: handling non-token-specific errors
{retries: 3}
)
const requestWithRetry = request => retry(
() => savedToken().then(request).catch(error => {
renewSavedToken()
throw error
}),
{ retries: 1 }
)
const get = (url, params) => requestWithRetry(getRequest(url, params))
const post = (url, body) => requestWithRetry(postRequest(url, body))
module.exports = {
get,
post
get,
post
}
'use strict'
const post = require('./fetch').post
const { post: postRequest } = require('./fetch')
const isString = require('lodash/isString')
const isDate = require('lodash/isDate')
const sortBy = require('lodash/sortBy')
const get = require('lodash/get')
const pick = require('lodash/pick')
const merge = require('lodash/merge')
const moment = require('moment')
require('moment-duration-format')
const momentTz = require('moment-timezone') // cheap hack for moment-duration-format fail
const retry = require('p-retry')
const clone = (x) => JSON.parse(JSON.stringify(x))
const { operator, createStation, buildTripId } = require('./helpers')
const createPrice = (j) => (p) => ({
class: +p.travelClass,
amount: +p.centsValue / 100,
currency: 'EUR',
fareType: p.priceType,
saleURL: (j.saleLink && j.saleLink.code) ? j.saleLink.code : null // todo: saleableOnline
class: +p.travelClass,
amount: +p.centsValue / 100,
currency: 'EUR',
fareType: p.priceType,
url: (j.saleLink && j.saleLink.code) ? j.saleLink.code : null // todo: saleableOnline
})
const createStation = (s) => ({
type: 'station',
id: s.code,
name: s.designation
})
const hashLegs = legs => legs.map(leg => [leg.tripId, leg.origin.id, leg.destination.id].join('@')).join('-')
const hashLeg = (date) => (s) => date+'@'+s.departureStation.code+'@'+s.departureTime+'@'+s.arrivalStation.code+'@'+s.arrivalTime+'@'+s.trainNumber+'@'+s.duration
const hashJourney = (sections, date) => sections.map(hashLeg(date)).join('-')
const createJourney = (date, formattedDate) => (j) => {
// legs
let lastTime = 0
const legs = []
const sections = sortBy(j.travelSections, (x) => x.sequenceNumber)
for (let section of sections) {
const leg = {
tripId: buildTripId(section.trainNumber, formattedDate),
origin: createStation(section.departureStation),
destination: createStation(section.arrivalStation),
departurePlatform: section.departurePlatform,
arrivalPlatform: section.arrivalPlatform,
line: {
type: 'line',
id: section.trainNumber + '',
name: section.trainNumber + '',
product: get(section, 'serviceCode.designation'),
productCode: get(section, 'serviceCode.code'),
mode: 'train', // @todo
public: true,
operator
},
mode: 'train', // todo
public: true,
operator
}
const createJourney = (date) => (j) => {
const journey = {
type: 'journey'
}
// departure time
const departureTime = +moment.duration(section.departureTime)
if (departureTime < lastTime) date.add(1, 'days')
leg.departure = moment.tz(date.format('DD.MM.YYYY') + ' ' + section.departureTime, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toISOString()
lastTime = departureTime
// legs
let lastTime = 0
const legs = []
const sections = sortBy(j.travelSections, (x) => x.sequenceNumber)
for(let section of sections){
const leg = {
origin: createStation(section.departureStation),
destination: createStation(section.arrivalStation),
departurePlatform: section.departurePlatform,
arrivalPlatform: section.arrivalPlatform,
trainNumber: section.trainNumber,
service: section.serviceCode ? {
name: section.serviceCode.designation,
code: section.serviceCode.code
} : null,
mode: 'train', // todo
public: true,
operator: {
type: 'operator',
id: 'cp',
name: 'Comboios de Portugal',
url: 'https://www.cp.pt/'
}
}
const stopovers = []
for (let s of section.trainStops) {
const stopover = {
type: 'stopover',
stop: {
type: 'station',
id: s.station.code,
name: s.station.designation
},
arrivalPlatform: s.platform ? s.platform + '' : null,
departurePlatform: s.platform ? s.platform + '' : null
}
// departure time
const departureTime = +moment.duration(section.departureTime)
if(departureTime < lastTime) date.add(1, 'days')
leg.departure = moment.tz(date.format('DD.MM.YYYY')+' '+section.departureTime, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toDate()
lastTime = departureTime
const arrivalTime = +moment.duration(s.arrival)
if (arrivalTime < lastTime) date.add(1, 'days')
stopover.arrival = moment.tz(date.format('DD.MM.YYYY') + ' ' + s.arrival, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toISOString()
lastTime = arrivalTime
const departureTime = +moment.duration(s.departure)
if (departureTime < lastTime) date.add(1, 'days')
stopover.departure = moment.tz(date.format('DD.MM.YYYY') + ' ' + s.departure, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toISOString()
lastTime = departureTime
const stops = []
for(let s of section.trainStops){
const station = {
type: 'station',
id: s.station.code,
name: s.station.designation,
platform: s.platform
}
// sort keys
stopovers.push(pick(stopover, ['type', 'stop', 'arrival', 'arrivalPlatform', 'departure', 'departurePlatform']))
}
const arrivalTime = +moment.duration(s.arrival)
if(arrivalTime < lastTime) date.add(1, 'days')
station.arrival = moment.tz(date.format('DD.MM.YYYY')+' '+s.arrival, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toDate()
lastTime = arrivalTime
const departureTime = +moment.duration(s.departure)
if(departureTime < lastTime) date.add(1, 'days')
station.departure = moment.tz(date.format('DD.MM.YYYY')+' '+s.departure, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toDate()
lastTime = departureTime
// arrival time
const arrivalTime = +moment.duration(section.arrivalTime)
if (arrivalTime < lastTime) date.add(1, 'days')
leg.arrival = moment.tz(date.format('DD.MM.YYYY') + ' ' + section.arrivalTime, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toISOString()
lastTime = arrivalTime
stops.push(station)
}
leg.stopovers = stopovers
// arrival time
const arrivalTime = +moment.duration(section.arrivalTime)
if(arrivalTime < lastTime) date.add(1, 'days')
leg.arrival = moment.tz(date.format('DD.MM.YYYY')+' '+section.arrivalTime, 'DD.MM.YYYY HH:mm', 'Europe/Lisbon').toDate()
lastTime = arrivalTime
// sort keys
legs.push(pick(leg, ['tripId', 'origin', 'destination', 'departure', 'departurePlatform', 'arrival', 'arrivalPlatform', 'line', 'mode', 'public', 'operator', 'stopovers']))
}
leg.stops = stops
const journey = {
type: 'journey',
id: hashLegs(legs),
legs
}
legs.push(leg)
}
// prices
const prices = j.basePrices.map(createPrice(j)).filter((x) => x.amount > 0)
const sortedPrices = sortBy(prices, 'amount')
if (sortedPrices.length > 0) {
journey.price = {
...prices[0],
fares: prices
}
}
// id
journey.id = hashJourney(sections, date.format("DD.MM.YYYY"))
return journey
}
journey.legs = legs
const defaults = () => ({
when: new Date()
})
// prices
let prices = j.basePrices.map(createPrice(j))
prices = sortBy(prices.filter((x) => x.amount > 0), (x) => x.amount)
if(prices.length > 0){
const lowestPrice = clone(prices[0])
journey.price = lowestPrice
const journeys = async (origin, destination, opt = {}) => {
if (isString(origin)) origin = { id: origin, type: 'station' }
if (isString(destination)) destination = { id: destination, type: 'station' }
if (!isString(origin.id)) throw new Error('invalid or missing origin id')
if (origin.type !== 'station') throw new Error('invalid or missing origin type, must be station')
if (!isString(destination.id)) throw new Error('invalid or missing destination id')
if (destination.type !== 'station') throw new Error('invalid or missing destination type, must be station')
if(prices.length > 1){
journey.price.fares = clone(prices)
}
}
origin = origin.id
destination = destination.id
return journey
}
const options = merge({}, defaults(), opt)
if (!isDate(options.when)) throw new Error('opt.when must be a JS date object')
const date = momentTz.tz(options.when, 'Europe/Lisbon')
const formattedDate = date.format('YYYY-MM-DD')
const journeys = (origin, destination, date = new Date()) => {
if(isString(origin)) origin = {id: origin, type: 'station'}
if(!isString(origin.id)) throw new Error('invalid or missing origin id')
if(origin.type !== 'station') throw new Error('invalid or missing origin type')
origin = origin.id
if(isString(destination)) destination = {id: destination, type: 'station'}
if(!isString(destination.id)) throw new Error('invalid or missing destination id')
if(destination.type !== 'station') throw new Error('invalid or missing destination type')
destination = destination.id
if(!isDate(date)){
throw new Error('invalid `date` parameter')
}
date = momentTz.tz(date, 'Europe/Lisbon')
const formattedDate = date.format('YYYY-MM-DD')
return post("https://api.cp.pt/cp-api/siv/travel/search", {
departureStationCode: origin,
arrivalStationCode: destination,
classes: [1,2], // todo
searchType: 3,
travelDate: formattedDate,
returnDate: null, // todo
timeLimit: null
})
.then((res) => res.outwardTrip.map(createJourney(date))) // todo: check if date = res.travelDate
const { outwardTrip } = await postRequest('https://api.cp.pt/cp-api/siv/travel/search', {
departureStationCode: origin,
arrivalStationCode: destination,
classes: [1, 2], // todo
searchType: 3,
travelDate: formattedDate,
returnDate: null, // todo
timeLimit: null
})
return outwardTrip.map(createJourney(date, formattedDate)) // todo: check if date = res.travelDate
}
module.exports = (origin, destination, date = new Date()) => retry(() => journeys(origin, destination, date), {retries: 3})
module.exports = journeys
'use strict'
const get = require('./fetch').get
const retry = require('p-retry')
const { get: getRequest } = require('./fetch')
const createStation = (s) => ({
type: 'station',
id: s.code,
name: s.designation,
coordinates: !(s.latitude && s.longitude) ? null : {
longitude: +s.longitude,
latitude: +s.latitude
}
type: 'station',
id: s.code,
name: s.designation,
location: !(s.latitude && s.longitude) ? undefined : {
type: 'location',
longitude: +s.longitude,
latitude: +s.latitude
}
})
const stations = () =>
get("https://api.cp.pt/cp-api/siv/stations/")
.then((res) => res.map(createStation))
getRequest('https://api.cp.pt/cp-api/siv/stations/')
.then((res) => res.map(createStation))
module.exports = () => retry(() => stations(), {retries: 3})
module.exports = stations
{
"name": "comboios",
"version": "0.1.0",
"description": "Comboios de Portugal (CP, Portugese Railways) API client.",
"version": "0.0.5",
"keywords": [
"api",
"client",
"comboios",
"cp",
"cp.pt",
"comboios",
"de",

@@ -13,34 +15,43 @@ "portugal",

"railway",
"train",
"api",
"client"
"train"
],
"author": "Julius Tens <mail@juliustens.eu>",
"homepage": "https://github.com/juliuste/comboios",
"bugs": "https://github.com/juliuste/comboios/issues",
"repository": "juliuste/comboios",
"bugs": "https://github.com/juliuste/comboios/issues",
"main": "./index.js",
"license": "ISC",
"author": "Julius Tens <mail@juliustens.eu>",
"files": [
"index.js",
"lib/*"
],
"main": "lib/index.js",
"scripts": {
"check-deps": "depcheck",
"fix": "eslint --fix lib test.js",
"lint": "eslint lib test.js",
"prepublishOnly": "npm test",
"test": "npm run lint && npm run check-deps && node test"
},
"dependencies": {
"got": "^9.1.0",
"lodash": "^4.17.10",
"moment": "^2.22.2",
"got": "^9.6.0",
"lodash": "^4.17.11",
"moment": "^2.24.0",
"moment-duration-format": "^2.2.2",
"moment-timezone": "^0.5.21",
"p-retry": "^2.0.0"
"moment-timezone": "^0.5.23",
"p-retry": "^4.0.0"
},
"devDependencies": {
"tape": "^4.9.1"
"depcheck": "^0.7.2",
"eslint": "^5.15.3",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"tape": "^4.10.1",
"tape-promise": "^4.0.0",
"validate-fptf": "^3.0.0"
},
"scripts": {
"test": "node test.js",
"prepublishOnly": "npm test"
},
"engines": {
"node": ">=8"
},
"license": "ISC"
}
}
# comboios
Client for the [Comboios de Porgual]() (CP, Portugese Railways) REST API. Inofficial, please ask CP for permission before using this module in production.
JavaScript client for the Portugese 🇵🇹 [Comboios de Portugal (CP)](https://www.cp.pt/) railway API. Complies with the friendly public transport format. Inofficial, using *CP* endpoints. Ask them for permission before using this module in production.

@@ -9,3 +9,3 @@ [![npm version](https://img.shields.io/npm/v/comboios.svg)](https://www.npmjs.com/package/comboios)

[![dependency status](https://img.shields.io/david/juliuste/comboios.svg)](https://david-dm.org/juliuste/comboios)
[![license](https://img.shields.io/github/license/juliuste/comboios.svg?style=flat)](LICENSE)
[![license](https://img.shields.io/github/license/juliuste/comboios.svg?style=flat)](license)
[![chat on gitter](https://badges.gitter.im/juliuste.svg)](https://gitter.im/juliuste)

@@ -16,3 +16,3 @@

```shell
npm install --save comboios
npm install comboios
```

@@ -22,15 +22,19 @@

This package mostly returns data in the [*Friendly Public Transport Format*](https://github.com/public-transport/friendly-public-transport-format):
```javascript
const comboios = require('comboios')
```
- [`stations()`](docs/stations.md) - List of operated stations
- [`departures(station, date = new Date())`](docs/departures.md) - Departures at a given station
- [`trains(trainNumber, date = new Date())`](docs/trains.md) - Schedule for a given train
- [`journeys(origin, destination, date = new Date())`](docs/journeys.md) - Journeys between stations
This package contains data in the [*Friendly Public Transport Format*](https://github.com/public-transport/friendly-public-transport-format).
- [`stations()`](docs/stations.md) - to get a list of operated stations such as `Lisboa - Oriente` or `Viana do Castelo`
- [`journeys(origin, destination, opt)`](docs/journeys.md) - to get routes between stations
- [`stopovers(station, opt)`](docs/stopovers.md) - to get departures and arrivals at a given station
- [`trip(id)`](docs/trip.md) - to get all stopovers for a given trip (train)
## See also
- [build-cp-gtfs](https://github.com/juliuste/build-cp-gtfs) - Generate CP GTFS using this module
- [european-transport-operators](https://github.com/public-transport/european-transport-operators) - List of european long-distance transport operators, available API endpoints, GTFS feeds and client modules.
## Contributing
If you found a bug, want to propose a feature or feel the urge to complain about your life, feel free to visit [the issues page](https://github.com/juliuste/comboios/issues).
If you found a bug or want to propose a feature, feel free to visit [the issues page](https://github.com/juliuste/comboios/issues).

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc