hubot-nextbus 2.1.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +16 -0
- package/.github/workflows/npm-publish.yml +30 -0
- package/README.md +1 -1
- package/index.js +6 -0
- package/package.json +15 -10
- package/script/bootstrap +2 -2
- package/script/test +1 -1
- package/src/nextbus.js +118 -0
- package/test/nextbus_test.js +215 -0
- package/.release-it.json +0 -8
- package/index.coffee +0 -12
- package/src/nextbus.coffee +0 -96
- package/test/nextbus_test.coffee +0 -147
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
browser: true,
|
|
4
|
+
commonjs: true,
|
|
5
|
+
es2021: true,
|
|
6
|
+
},
|
|
7
|
+
extends: 'airbnb-base',
|
|
8
|
+
overrides: [
|
|
9
|
+
],
|
|
10
|
+
parserOptions: {
|
|
11
|
+
ecmaVersion: 'latest',
|
|
12
|
+
},
|
|
13
|
+
rules: {
|
|
14
|
+
'no-param-reassign': ['error', { props: false }],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
on:
|
|
2
|
+
push:
|
|
3
|
+
branches: main
|
|
4
|
+
|
|
5
|
+
name: npm-publish
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-node@v3
|
|
16
|
+
with:
|
|
17
|
+
node-version: "20"
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm test
|
|
20
|
+
- uses: JS-DevTools/npm-publish@v3
|
|
21
|
+
id: publish
|
|
22
|
+
with:
|
|
23
|
+
token: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
24
|
+
- if: ${{ steps.publish.outputs.type }}
|
|
25
|
+
name: Create Release
|
|
26
|
+
env:
|
|
27
|
+
GH_TOKEN: ${{ github.token }}
|
|
28
|
+
run: |
|
|
29
|
+
VERSION="v${{ steps.publish.outputs.version }}"
|
|
30
|
+
gh release create $VERSION --generate-notes
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Hubot Nextbus
|
|
1
|
+
# Hubot Nextbus
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/js/hubot-nextbus) [](https://github.com/transitnownash/hubot-nextbus/actions/workflows/nodejs.yml)
|
|
4
4
|
|
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hubot-nextbus",
|
|
3
3
|
"description": "Shows when the next transit vehicle will arrive at a particular stop.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"author": "Stephen Yeargin <stephen@yearg.in>",
|
|
6
6
|
"homepage": "https://github.com/transitnownash/hubot-nextbus",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,27 +21,32 @@
|
|
|
21
21
|
"url": "https://github.com/transitnownash/hubot-nextbus/issues"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"hubot": "
|
|
24
|
+
"hubot": ">=3 || 0.0.0-development"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"chai": "^4.3.
|
|
28
|
-
"
|
|
27
|
+
"chai": "^4.3.10",
|
|
28
|
+
"eslint": "^8.56.0",
|
|
29
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
30
|
+
"eslint-plugin-import": "^2.29.1",
|
|
31
|
+
"eslint-plugin-n": "^16.5.0",
|
|
32
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
29
33
|
"hubot-test-helper": "^1.9.0",
|
|
30
34
|
"husky": "^8.0.3",
|
|
31
35
|
"mocha": "^10.2.0",
|
|
32
|
-
"nock": "^13.
|
|
33
|
-
"
|
|
34
|
-
"sinon": "^15.1.0",
|
|
36
|
+
"nock": "^13.4.0",
|
|
37
|
+
"sinon": "^17.0.1",
|
|
35
38
|
"sinon-chai": "^3.7.0"
|
|
36
39
|
},
|
|
37
|
-
"main": "index.
|
|
40
|
+
"main": "index.js",
|
|
38
41
|
"scripts": {
|
|
39
|
-
"
|
|
40
|
-
"test": "mocha --require coffee-script/register \"test/**/*.coffee\" --reporter spec",
|
|
42
|
+
"test": "script/test",
|
|
41
43
|
"prepare": "husky install"
|
|
42
44
|
},
|
|
43
45
|
"dependencies": {
|
|
44
46
|
"ascii-table": "^0.0.9",
|
|
45
47
|
"moment": "^2.29.4"
|
|
48
|
+
},
|
|
49
|
+
"volta": {
|
|
50
|
+
"node": "18.19.0"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/script/bootstrap
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export NODE_ENV=development
|
|
5
5
|
|
|
6
6
|
# Load environment specific environment variables
|
|
7
|
-
if [ -f .env ]; then
|
|
7
|
+
if [ -f .env ]; then
|
|
8
8
|
source .env
|
|
9
9
|
fi
|
|
10
10
|
|
|
@@ -14,5 +14,5 @@ fi
|
|
|
14
14
|
|
|
15
15
|
npm install
|
|
16
16
|
|
|
17
|
-
# Make sure
|
|
17
|
+
# Make sure mocha is on the path
|
|
18
18
|
export PATH="node_modules/.bin:$PATH"
|
package/script/test
CHANGED
package/src/nextbus.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Description:
|
|
2
|
+
// Get the next bus for a particular stop
|
|
3
|
+
//
|
|
4
|
+
// Configuration:
|
|
5
|
+
// HUBOT_NEXTBUS_BASE_URL - URL of a `gtfs-rails-api` instance
|
|
6
|
+
// HUBOT_NEXTBUS_LAT_LON - Default location for stop search
|
|
7
|
+
// HUBOT_NEXTBUS_STOP_ID - Default stop for `hubot nextbus`
|
|
8
|
+
//
|
|
9
|
+
// Commands:
|
|
10
|
+
// hubot nextbus
|
|
11
|
+
// hubot nextbus stops
|
|
12
|
+
// hubot nextbus stop <stop-identifier>
|
|
13
|
+
//
|
|
14
|
+
// Author:
|
|
15
|
+
// stephenyeargin
|
|
16
|
+
|
|
17
|
+
const moment = require('moment');
|
|
18
|
+
const AsciiTable = require('ascii-table');
|
|
19
|
+
|
|
20
|
+
module.exports = (robot) => {
|
|
21
|
+
const baseURL = process.env.HUBOT_NEXTBUS_BASE_URL || 'https://gtfs.transitnownash.org';
|
|
22
|
+
const latlon = process.env.HUBOT_NEXTBUS_LAT_LON;
|
|
23
|
+
const defaultStopId = process.env.HUBOT_NEXTBUS_STOP_ID;
|
|
24
|
+
|
|
25
|
+
const getAPIResponse = (path, msg, cb) => {
|
|
26
|
+
const url = `${baseURL}/${path}`;
|
|
27
|
+
robot.logger.debug(url);
|
|
28
|
+
robot.http(url)
|
|
29
|
+
.get()((err, res, body) => {
|
|
30
|
+
const response = JSON.parse(body);
|
|
31
|
+
if (err) {
|
|
32
|
+
msg.send(err);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (response.error) {
|
|
36
|
+
msg.send(response.error);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!body) {
|
|
40
|
+
msg.send('No data returned.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
cb(response);
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const formatTripTimeAsMoment = (timeStr) => {
|
|
48
|
+
if (/^2[4-9]:/.test(timeStr)) {
|
|
49
|
+
// eslint-disable-next-line no-param-reassign
|
|
50
|
+
timeStr = (parseInt(timeStr.substr(0, 2), 10) - 24) + timeStr.substr(2, 8);
|
|
51
|
+
return moment(`${moment().format('YYYY-MM-DD')} ${timeStr.padStart(8, '0')}`).add(1, 'days');
|
|
52
|
+
}
|
|
53
|
+
return moment(`${moment().format('YYYY-MM-DD')} ${timeStr.trim().padStart(8, '0')}`);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const queryStopById = (stopId, msg) => getAPIResponse('agencies.json', msg, (agencies) => {
|
|
57
|
+
// Override timezone for moment() calls
|
|
58
|
+
process.env.TZ = agencies.data[0].agency_timezone;
|
|
59
|
+
robot.logger.debug(process.env.TZ);
|
|
60
|
+
robot.logger.debug('Current Time:', moment());
|
|
61
|
+
getAPIResponse(`stops/${stopId}/trips.json?per_page=2000`, msg, (trips) => {
|
|
62
|
+
const nextTrips = trips.data.filter((trip) => {
|
|
63
|
+
const tripTime = formatTripTimeAsMoment(trip.stop_times[0].arrival_time);
|
|
64
|
+
return tripTime.isAfter(moment(), 'second');
|
|
65
|
+
});
|
|
66
|
+
robot.logger.debug(nextTrips);
|
|
67
|
+
if (nextTrips.length === 0) {
|
|
68
|
+
msg.send('The last bus has already run for today.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const table = new AsciiTable();
|
|
73
|
+
const {
|
|
74
|
+
stop,
|
|
75
|
+
} = nextTrips[0].stop_times[0];
|
|
76
|
+
msg.send(`Upcoming Trips for [${stop.stop_gid}] ${stop.stop_name}`);
|
|
77
|
+
nextTrips.slice(0, 5).forEach((trip) => {
|
|
78
|
+
const tripTime = formatTripTimeAsMoment(trip.stop_times[0].arrival_time);
|
|
79
|
+
table.addRow([formatTripTimeAsMoment(trip.stop_times[0].arrival_time).format('LT'), `#${trip.route_gid} - ${trip.trip_headsign}`, tripTime.fromNow()]);
|
|
80
|
+
});
|
|
81
|
+
table.removeBorder();
|
|
82
|
+
msg.send(table.toString());
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// query the default stop ID or location's closest bus stop
|
|
87
|
+
robot.respond(/(?:bus|nextbus)(?: me)?$/i, (msg) => {
|
|
88
|
+
if (defaultStopId) {
|
|
89
|
+
queryStopById(defaultStopId, msg);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getAPIResponse(`stops/near/${latlon}/1000.json?per_page=5`, msg, (stops) => {
|
|
94
|
+
if (stops.total > 0) {
|
|
95
|
+
queryStopById(stops.data[0].stop_gid, msg);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
msg.send(`No stops found near ${latlon}`);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// get a list of nearby stops
|
|
103
|
+
robot.respond(/(?:bus|nextbus) stops$/i, (msg) => getAPIResponse(`stops/near/${latlon}/1000.json?per_page=5`, msg, (stops) => {
|
|
104
|
+
msg.send('List of nearby stops:');
|
|
105
|
+
const output = [];
|
|
106
|
+
stops.data.forEach((stop) => {
|
|
107
|
+
output.push(`- [${stop.stop_gid}] ${stop.stop_name}`);
|
|
108
|
+
});
|
|
109
|
+
msg.send(output.join('\n'));
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
// get a particular stop's next bus
|
|
113
|
+
robot.respond(/(?:bus|nextbus) stop ([A-Z0-9_]+)$/i, (msg) => {
|
|
114
|
+
const stopId = msg.match[1];
|
|
115
|
+
robot.logger.debug(stopId);
|
|
116
|
+
queryStopById(stopId, msg);
|
|
117
|
+
});
|
|
118
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/* eslint-disable func-names */
|
|
2
|
+
/* global describe beforeEach afterEach context it */
|
|
3
|
+
|
|
4
|
+
const Helper = require('hubot-test-helper');
|
|
5
|
+
const chai = require('chai');
|
|
6
|
+
const nock = require('nock');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
expect,
|
|
10
|
+
} = chai;
|
|
11
|
+
|
|
12
|
+
const helper = new Helper('../src/nextbus.js');
|
|
13
|
+
|
|
14
|
+
// Alter time as test runs
|
|
15
|
+
const originalDateNow = Date.now;
|
|
16
|
+
|
|
17
|
+
describe('hubot-nextbus', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
nock.disableNetConnect();
|
|
20
|
+
nock('https://gtfs.transitnownash.org')
|
|
21
|
+
.get('/stops/near/36.1650,-86.78404/1000.json?per_page=5')
|
|
22
|
+
.replyWithFile(200, `${__dirname}/fixtures/stops-near-gps.json`);
|
|
23
|
+
nock('https://gtfs.transitnownash.org')
|
|
24
|
+
.get('/agencies.json')
|
|
25
|
+
.replyWithFile(200, `${__dirname}/fixtures/agencies.json`);
|
|
26
|
+
nock('https://gtfs.transitnownash.org')
|
|
27
|
+
.get('/stops/CHA7AWN/trips.json?per_page=2000')
|
|
28
|
+
.replyWithFile(200, `${__dirname}/fixtures/stops-CHA7AWN-trips.json`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
nock.cleanAll();
|
|
33
|
+
Date.now = originalDateNow;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
context('regular tests with latitude/longitude set', () => {
|
|
37
|
+
beforeEach(function () {
|
|
38
|
+
Date.now = () => Date.parse('Fri, 1 Oct 2021 12:00:00 UTC');
|
|
39
|
+
process.env.HUBOT_NEXTBUS_LAT_LON = '36.1650,-86.78404';
|
|
40
|
+
this.room = helper.createRoom();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(function () {
|
|
44
|
+
this.room.destroy();
|
|
45
|
+
delete process.env.HUBOT_NEXTBUS_LAT_LON;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// hubot nextbus
|
|
49
|
+
it('returns the next bus for closest stop', function (done) {
|
|
50
|
+
const selfRoom = this.room;
|
|
51
|
+
selfRoom.user.say('alice', '@hubot nextbus');
|
|
52
|
+
setTimeout(
|
|
53
|
+
() => {
|
|
54
|
+
try {
|
|
55
|
+
expect(selfRoom.messages).to.eql([
|
|
56
|
+
['alice', '@hubot nextbus'],
|
|
57
|
+
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB'],
|
|
58
|
+
[
|
|
59
|
+
'hubot',
|
|
60
|
+
' 7:01 AM #50 - CHARLOTTE WALMART in a minute \n'
|
|
61
|
+
+ ' 7:15 AM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n'
|
|
62
|
+
+ ' 7:16 AM #50 - CHARLOTTE WALMART in 16 minutes \n'
|
|
63
|
+
+ ' 7:31 AM #50 - CHARLOTTE WALMART in 31 minutes \n'
|
|
64
|
+
+ ' 7:35 AM #17 - GREEN HILLS VIA 12TH AVE S in 36 minutes ',
|
|
65
|
+
],
|
|
66
|
+
]);
|
|
67
|
+
done();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
done(err);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
100,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// hubot nextbus stop <id>
|
|
77
|
+
it('returns the next bus for a particular stop', function (done) {
|
|
78
|
+
const selfRoom = this.room;
|
|
79
|
+
selfRoom.user.say('alice', '@hubot nextbus stop CHA7AWN');
|
|
80
|
+
setTimeout(
|
|
81
|
+
() => {
|
|
82
|
+
try {
|
|
83
|
+
expect(selfRoom.messages).to.eql([
|
|
84
|
+
['alice', '@hubot nextbus stop CHA7AWN'],
|
|
85
|
+
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB'],
|
|
86
|
+
[
|
|
87
|
+
'hubot',
|
|
88
|
+
' 7:01 AM #50 - CHARLOTTE WALMART in a minute \n'
|
|
89
|
+
+ ' 7:15 AM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n'
|
|
90
|
+
+ ' 7:16 AM #50 - CHARLOTTE WALMART in 16 minutes \n'
|
|
91
|
+
+ ' 7:31 AM #50 - CHARLOTTE WALMART in 31 minutes \n'
|
|
92
|
+
+ ' 7:35 AM #17 - GREEN HILLS VIA 12TH AVE S in 36 minutes ',
|
|
93
|
+
],
|
|
94
|
+
]);
|
|
95
|
+
done();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
done(err);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
100,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// hubot nextbus stops
|
|
105
|
+
it('returns the list of nearby stops', function (done) {
|
|
106
|
+
const selfRoom = this.room;
|
|
107
|
+
selfRoom.user.say('alice', '@hubot nextbus stops');
|
|
108
|
+
setTimeout(
|
|
109
|
+
() => {
|
|
110
|
+
try {
|
|
111
|
+
expect(selfRoom.messages).to.eql([
|
|
112
|
+
['alice', '@hubot nextbus stops'],
|
|
113
|
+
['hubot', 'List of nearby stops:'],
|
|
114
|
+
[
|
|
115
|
+
'hubot',
|
|
116
|
+
'- [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB\n'
|
|
117
|
+
+ '- [CHA7AEN] CHARLOTTE AVE & 7TH AVE N EB\n'
|
|
118
|
+
+ '- [6AVDEASN] 6TH AVE & DEADERICK ST SB\n'
|
|
119
|
+
+ '- [6AVDEANN] 6TH AVE N & DEADERICK ST NB\n'
|
|
120
|
+
+ '- [UNI7AWN] UNION ST & 7TH AVE N WB',
|
|
121
|
+
],
|
|
122
|
+
]);
|
|
123
|
+
done();
|
|
124
|
+
} catch (err) {
|
|
125
|
+
done(err);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
100,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
context('regular tests with default stop ID set', () => {
|
|
134
|
+
beforeEach(function () {
|
|
135
|
+
Date.now = () => Date.parse('Fri, 1 Oct 2021 12:00:00 UTC');
|
|
136
|
+
process.env.HUBOT_NEXTBUS_LAT_LON = '0,0';
|
|
137
|
+
process.env.HUBOT_NEXTBUS_STOP_ID = 'CHA7AWN';
|
|
138
|
+
this.room = helper.createRoom();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
afterEach(function () {
|
|
142
|
+
this.room.destroy();
|
|
143
|
+
delete process.env.HUBOT_NEXTBUS_LAT_LON;
|
|
144
|
+
delete process.env.HUBOT_NEXTBUS_STOP_ID;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// hubot nextbus
|
|
148
|
+
it('returns the next bus for closest stop', function (done) {
|
|
149
|
+
const selfRoom = this.room;
|
|
150
|
+
selfRoom.user.say('alice', '@hubot nextbus');
|
|
151
|
+
setTimeout(
|
|
152
|
+
() => {
|
|
153
|
+
try {
|
|
154
|
+
expect(selfRoom.messages).to.eql([
|
|
155
|
+
['alice', '@hubot nextbus'],
|
|
156
|
+
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB'],
|
|
157
|
+
[
|
|
158
|
+
'hubot',
|
|
159
|
+
' 7:01 AM #50 - CHARLOTTE WALMART in a minute \n'
|
|
160
|
+
+ ' 7:15 AM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n'
|
|
161
|
+
+ ' 7:16 AM #50 - CHARLOTTE WALMART in 16 minutes \n'
|
|
162
|
+
+ ' 7:31 AM #50 - CHARLOTTE WALMART in 31 minutes \n'
|
|
163
|
+
+ ' 7:35 AM #17 - GREEN HILLS VIA 12TH AVE S in 36 minutes ',
|
|
164
|
+
],
|
|
165
|
+
]);
|
|
166
|
+
done();
|
|
167
|
+
} catch (err) {
|
|
168
|
+
done(err);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
100,
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
context('time spans days', () => {
|
|
177
|
+
beforeEach(function () {
|
|
178
|
+
Date.now = () => Date.parse('Fri, 1 Oct 2021 04:00:00 UTC');
|
|
179
|
+
process.env.HUBOT_NEXTBUS_LAT_LON = '36.1650,-86.78404';
|
|
180
|
+
this.room = helper.createRoom();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
afterEach(function () {
|
|
184
|
+
this.room.destroy();
|
|
185
|
+
delete process.env.HUBOT_NEXTBUS_LAT_LON;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// hubot nextbus
|
|
189
|
+
it('returns the next bus for closest stop', function (done) {
|
|
190
|
+
const selfRoom = this.room;
|
|
191
|
+
selfRoom.user.say('alice', '@hubot nextbus');
|
|
192
|
+
setTimeout(
|
|
193
|
+
() => {
|
|
194
|
+
try {
|
|
195
|
+
expect(selfRoom.messages).to.eql([
|
|
196
|
+
['alice', '@hubot nextbus'],
|
|
197
|
+
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB'],
|
|
198
|
+
[
|
|
199
|
+
'hubot',
|
|
200
|
+
' 11:16 PM #50 - CHARLOTTE WALMART in 16 minutes \n'
|
|
201
|
+
+ ' 11:15 PM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n'
|
|
202
|
+
+ ' 11:46 PM #50 - CHARLOTTE WALMART in an hour \n'
|
|
203
|
+
+ ' 12:16 AM #50 - CHARLOTTE WALMART in an hour ',
|
|
204
|
+
],
|
|
205
|
+
]);
|
|
206
|
+
done();
|
|
207
|
+
} catch (err) {
|
|
208
|
+
done(err);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
100,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
package/.release-it.json
DELETED
package/index.coffee
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
fs = require 'fs'
|
|
2
|
-
path = require 'path'
|
|
3
|
-
|
|
4
|
-
module.exports = (robot, scripts) ->
|
|
5
|
-
scriptsPath = path.resolve(__dirname, 'src')
|
|
6
|
-
fs.exists scriptsPath, (exists) ->
|
|
7
|
-
if exists
|
|
8
|
-
for script in fs.readdirSync(scriptsPath)
|
|
9
|
-
if scripts? and '*' not in scripts
|
|
10
|
-
robot.loadFile(scriptsPath, script) if script in scripts
|
|
11
|
-
else
|
|
12
|
-
robot.loadFile(scriptsPath, script)
|
package/src/nextbus.coffee
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Description:
|
|
2
|
-
# Get the next bus for a particular stop
|
|
3
|
-
#
|
|
4
|
-
# Configuration:
|
|
5
|
-
# HUBOT_NEXTBUS_BASE_URL - URL of a `gtfs-rails-api` instance
|
|
6
|
-
# HUBOT_NEXTBUS_LAT_LON - Default location for stop search
|
|
7
|
-
# HUBOT_NEXTBUS_STOP_ID - Default stop for `hubot nextbus`
|
|
8
|
-
#
|
|
9
|
-
# Commands:
|
|
10
|
-
# hubot nextbus
|
|
11
|
-
# hubot nextbus stops
|
|
12
|
-
# hubot nextbus stop <stop-identifier>
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
# Author:
|
|
16
|
-
# stephenyeargin
|
|
17
|
-
|
|
18
|
-
module.exports = (robot) ->
|
|
19
|
-
moment = require('moment')
|
|
20
|
-
AsciiTable = require('ascii-table')
|
|
21
|
-
baseURL = process.env.HUBOT_NEXTBUS_BASE_URL || 'https://gtfs.transitnownash.org'
|
|
22
|
-
latlon = process.env.HUBOT_NEXTBUS_LAT_LON
|
|
23
|
-
defaultStopId = process.env.HUBOT_NEXTBUS_STOP_ID
|
|
24
|
-
|
|
25
|
-
# query the default stop ID or location's closest bus stop
|
|
26
|
-
robot.respond /(?:bus|nextbus)(?: me)?$/i, (msg) ->
|
|
27
|
-
if defaultStopId
|
|
28
|
-
queryStopById defaultStopId, msg
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
getAPIResponse "stops/near/#{latlon}/1000.json?per_page=5", msg, (stops) ->
|
|
32
|
-
if stops.total > 0
|
|
33
|
-
queryStopById stops.data[0].stop_gid, msg
|
|
34
|
-
else
|
|
35
|
-
msg.send "No stops found near #{latlon}"
|
|
36
|
-
|
|
37
|
-
# get a list of nearby stops
|
|
38
|
-
robot.respond /(?:bus|nextbus) stops$/i, (msg) ->
|
|
39
|
-
getAPIResponse "stops/near/#{latlon}/1000.json?per_page=5", msg, (stops) ->
|
|
40
|
-
msg.send "List of nearby stops:"
|
|
41
|
-
output = []
|
|
42
|
-
for stop in stops.data
|
|
43
|
-
output.push "- [#{stop.stop_gid}] #{stop.stop_name}"
|
|
44
|
-
msg.send output.join("\n")
|
|
45
|
-
|
|
46
|
-
# get a particular stop's next bus
|
|
47
|
-
robot.respond /(?:bus|nextbus) stop ([A-Z0-9_]+)$/i, (msg) ->
|
|
48
|
-
stop_id = msg.match[1]
|
|
49
|
-
robot.logger.debug stop_id
|
|
50
|
-
queryStopById stop_id, msg
|
|
51
|
-
|
|
52
|
-
queryStopById = (stop_id, msg) ->
|
|
53
|
-
getAPIResponse 'agencies.json', msg, (agencies) ->
|
|
54
|
-
# Override timezone for moment() calls
|
|
55
|
-
process.env.TZ = agencies.data[0].agency_timezone
|
|
56
|
-
robot.logger.debug process.env.TZ
|
|
57
|
-
robot.logger.debug 'Current Time:', moment()
|
|
58
|
-
getAPIResponse "stops/#{stop_id}/trips.json?per_page=2000", msg, (trips) ->
|
|
59
|
-
nextTrips = trips.data.filter((trip) =>
|
|
60
|
-
tripTime = formatTripTimeAsMoment(trip.stop_times[0].arrival_time)
|
|
61
|
-
return tripTime.isAfter(moment(), 'second')
|
|
62
|
-
)
|
|
63
|
-
robot.logger.debug nextTrips
|
|
64
|
-
if nextTrips.length == 0
|
|
65
|
-
return msg.send "The last bus has already run for today."
|
|
66
|
-
|
|
67
|
-
table = new AsciiTable()
|
|
68
|
-
stop = nextTrips[0].stop_times[0].stop
|
|
69
|
-
msg.send "Upcoming Trips for [#{stop.stop_gid}] #{stop.stop_name}"
|
|
70
|
-
for trip in nextTrips.slice(0, 5)
|
|
71
|
-
tripTime = formatTripTimeAsMoment(trip.stop_times[0].arrival_time)
|
|
72
|
-
table.addRow [formatTripTimeAsMoment(trip.stop_times[0].arrival_time).format('LT'), "##{trip.route_gid} - #{trip.trip_headsign}", tripTime.fromNow()]
|
|
73
|
-
table.removeBorder()
|
|
74
|
-
msg.send table.toString()
|
|
75
|
-
|
|
76
|
-
formatTripTimeAsMoment = (timeStr) ->
|
|
77
|
-
if RegExp('^2[4-9]:').test(timeStr)
|
|
78
|
-
timeStr = (parseInt(timeStr.substr(0,2), 10) - 24) + timeStr.substr(2,8)
|
|
79
|
-
tripTime = moment(moment().format('YYYY-MM-DD') + ' ' + timeStr.padStart(8, '0')).add(1, 'days')
|
|
80
|
-
else
|
|
81
|
-
tripTime = moment(moment().format('YYYY-MM-DD') + ' ' + timeStr.trim().padStart(8, '0'))
|
|
82
|
-
return tripTime
|
|
83
|
-
|
|
84
|
-
getAPIResponse = (path, msg, cb) ->
|
|
85
|
-
url = "#{baseURL}/#{path}"
|
|
86
|
-
robot.logger.debug url
|
|
87
|
-
robot.http(url)
|
|
88
|
-
.get() (err, res, body) ->
|
|
89
|
-
response = JSON.parse(body)
|
|
90
|
-
if err
|
|
91
|
-
return msg.send err
|
|
92
|
-
if response.error
|
|
93
|
-
return msg.send response.error
|
|
94
|
-
if !body
|
|
95
|
-
return msg.send "No data returned."
|
|
96
|
-
cb(response)
|
package/test/nextbus_test.coffee
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
Helper = require('hubot-test-helper')
|
|
2
|
-
chai = require 'chai'
|
|
3
|
-
nock = require 'nock'
|
|
4
|
-
|
|
5
|
-
expect = chai.expect
|
|
6
|
-
|
|
7
|
-
helper = new Helper('../src/nextbus.coffee')
|
|
8
|
-
|
|
9
|
-
# Alter time as test runs
|
|
10
|
-
originalDateNow = Date.now
|
|
11
|
-
|
|
12
|
-
describe 'hubot-nextbus', ->
|
|
13
|
-
beforeEach ->
|
|
14
|
-
nock.disableNetConnect()
|
|
15
|
-
nock('https://gtfs.transitnownash.org')
|
|
16
|
-
.get('/stops/near/36.1650,-86.78404/1000.json?per_page=5')
|
|
17
|
-
.replyWithFile(200, __dirname + '/fixtures/stops-near-gps.json')
|
|
18
|
-
nock('https://gtfs.transitnownash.org')
|
|
19
|
-
.get('/agencies.json')
|
|
20
|
-
.replyWithFile(200, __dirname + '/fixtures/agencies.json')
|
|
21
|
-
nock('https://gtfs.transitnownash.org')
|
|
22
|
-
.get('/stops/CHA7AWN/trips.json?per_page=2000')
|
|
23
|
-
.replyWithFile(200, __dirname + '/fixtures/stops-CHA7AWN-trips.json')
|
|
24
|
-
|
|
25
|
-
afterEach ->
|
|
26
|
-
nock.cleanAll()
|
|
27
|
-
Date.now = originalDateNow
|
|
28
|
-
|
|
29
|
-
context 'regular tests with latitude/longitude set', ->
|
|
30
|
-
beforeEach ->
|
|
31
|
-
Date.now = () ->
|
|
32
|
-
return Date.parse('Fri, 1 Oct 2021 12:00:00 UTC')
|
|
33
|
-
process.env.HUBOT_NEXTBUS_LAT_LON = '36.1650,-86.78404'
|
|
34
|
-
@room = helper.createRoom()
|
|
35
|
-
|
|
36
|
-
afterEach ->
|
|
37
|
-
@room.destroy()
|
|
38
|
-
delete process.env.HUBOT_NEXTBUS_LAT_LON
|
|
39
|
-
|
|
40
|
-
# hubot nextbus
|
|
41
|
-
it 'returns the next bus for closest stop', (done) ->
|
|
42
|
-
selfRoom = @room
|
|
43
|
-
selfRoom.user.say('alice', '@hubot nextbus')
|
|
44
|
-
setTimeout(() ->
|
|
45
|
-
try
|
|
46
|
-
expect(selfRoom.messages).to.eql [
|
|
47
|
-
['alice', '@hubot nextbus']
|
|
48
|
-
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB']
|
|
49
|
-
['hubot', ' 7:01 AM #50 - CHARLOTTE WALMART in a minute \n 7:15 AM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n 7:16 AM #50 - CHARLOTTE WALMART in 16 minutes \n 7:31 AM #50 - CHARLOTTE WALMART in 31 minutes \n 7:35 AM #17 - GREEN HILLS VIA 12TH AVE S in 36 minutes ']
|
|
50
|
-
]
|
|
51
|
-
done()
|
|
52
|
-
catch err
|
|
53
|
-
done err
|
|
54
|
-
return
|
|
55
|
-
, 1000)
|
|
56
|
-
|
|
57
|
-
# hubot nextbus stop <id>
|
|
58
|
-
it 'returns the next bus for a particular stop', (done) ->
|
|
59
|
-
selfRoom = @room
|
|
60
|
-
selfRoom.user.say('alice', '@hubot nextbus stop CHA7AWN')
|
|
61
|
-
setTimeout(() ->
|
|
62
|
-
try
|
|
63
|
-
expect(selfRoom.messages).to.eql [
|
|
64
|
-
['alice', '@hubot nextbus stop CHA7AWN']
|
|
65
|
-
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB']
|
|
66
|
-
['hubot', ' 7:01 AM #50 - CHARLOTTE WALMART in a minute \n 7:15 AM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n 7:16 AM #50 - CHARLOTTE WALMART in 16 minutes \n 7:31 AM #50 - CHARLOTTE WALMART in 31 minutes \n 7:35 AM #17 - GREEN HILLS VIA 12TH AVE S in 36 minutes ']
|
|
67
|
-
]
|
|
68
|
-
done()
|
|
69
|
-
catch err
|
|
70
|
-
done err
|
|
71
|
-
return
|
|
72
|
-
, 1000)
|
|
73
|
-
|
|
74
|
-
# hubot nextbus stops
|
|
75
|
-
it 'returns the list of nearby stops', (done) ->
|
|
76
|
-
selfRoom = @room
|
|
77
|
-
selfRoom.user.say('alice', '@hubot nextbus stops')
|
|
78
|
-
setTimeout(() ->
|
|
79
|
-
try
|
|
80
|
-
expect(selfRoom.messages).to.eql [
|
|
81
|
-
['alice', '@hubot nextbus stops']
|
|
82
|
-
['hubot', 'List of nearby stops:']
|
|
83
|
-
['hubot', '- [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB\n- [CHA7AEN] CHARLOTTE AVE & 7TH AVE N EB\n- [6AVDEASN] 6TH AVE & DEADERICK ST SB\n- [6AVDEANN] 6TH AVE N & DEADERICK ST NB\n- [UNI7AWN] UNION ST & 7TH AVE N WB']
|
|
84
|
-
]
|
|
85
|
-
done()
|
|
86
|
-
catch err
|
|
87
|
-
done err
|
|
88
|
-
return
|
|
89
|
-
, 1000)
|
|
90
|
-
|
|
91
|
-
context 'regular tests with default stop ID set', ->
|
|
92
|
-
beforeEach ->
|
|
93
|
-
Date.now = () ->
|
|
94
|
-
return Date.parse('Fri, 1 Oct 2021 12:00:00 UTC')
|
|
95
|
-
process.env.HUBOT_NEXTBUS_LAT_LON = '0,0'
|
|
96
|
-
process.env.HUBOT_NEXTBUS_STOP_ID = 'CHA7AWN'
|
|
97
|
-
@room = helper.createRoom()
|
|
98
|
-
|
|
99
|
-
afterEach ->
|
|
100
|
-
@room.destroy()
|
|
101
|
-
delete process.env.HUBOT_NEXTBUS_LAT_LON
|
|
102
|
-
delete process.env.HUBOT_NEXTBUS_STOP_ID
|
|
103
|
-
|
|
104
|
-
# hubot nextbus
|
|
105
|
-
it 'returns the next bus for closest stop', (done) ->
|
|
106
|
-
selfRoom = @room
|
|
107
|
-
selfRoom.user.say('alice', '@hubot nextbus')
|
|
108
|
-
setTimeout(() ->
|
|
109
|
-
try
|
|
110
|
-
expect(selfRoom.messages).to.eql [
|
|
111
|
-
['alice', '@hubot nextbus']
|
|
112
|
-
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB']
|
|
113
|
-
['hubot', ' 7:01 AM #50 - CHARLOTTE WALMART in a minute \n 7:15 AM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n 7:16 AM #50 - CHARLOTTE WALMART in 16 minutes \n 7:31 AM #50 - CHARLOTTE WALMART in 31 minutes \n 7:35 AM #17 - GREEN HILLS VIA 12TH AVE S in 36 minutes ']
|
|
114
|
-
]
|
|
115
|
-
done()
|
|
116
|
-
catch err
|
|
117
|
-
done err
|
|
118
|
-
return
|
|
119
|
-
, 1000)
|
|
120
|
-
|
|
121
|
-
context 'time spans days', ->
|
|
122
|
-
beforeEach ->
|
|
123
|
-
Date.now = () ->
|
|
124
|
-
return Date.parse('Fri, 1 Oct 2021 04:00:00 UTC')
|
|
125
|
-
process.env.HUBOT_NEXTBUS_LAT_LON = '36.1650,-86.78404'
|
|
126
|
-
@room = helper.createRoom()
|
|
127
|
-
|
|
128
|
-
afterEach ->
|
|
129
|
-
@room.destroy()
|
|
130
|
-
delete process.env.HUBOT_NEXTBUS_LAT_LON
|
|
131
|
-
|
|
132
|
-
# hubot nextbus
|
|
133
|
-
it 'returns the next bus for closest stop', (done) ->
|
|
134
|
-
selfRoom = @room
|
|
135
|
-
selfRoom.user.say('alice', '@hubot nextbus')
|
|
136
|
-
setTimeout(() ->
|
|
137
|
-
try
|
|
138
|
-
expect(selfRoom.messages).to.eql [
|
|
139
|
-
['alice', '@hubot nextbus']
|
|
140
|
-
['hubot', 'Upcoming Trips for [CHA7AWN] CHARLOTTE AVE & 7TH AVE N WB']
|
|
141
|
-
['hubot', ' 11:16 PM #50 - CHARLOTTE WALMART in 16 minutes \n 11:15 PM #17 - GREEN HILLS VIA 12TH AVE S in 16 minutes \n 11:46 PM #50 - CHARLOTTE WALMART in an hour \n 12:16 AM #50 - CHARLOTTE WALMART in an hour ']
|
|
142
|
-
]
|
|
143
|
-
done()
|
|
144
|
-
catch err
|
|
145
|
-
done err
|
|
146
|
-
return
|
|
147
|
-
, 1000)
|