hubot-nextbus 2.3.0 → 2.4.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/.github/dependabot.yml +8 -0
- package/.github/workflows/npm-publish.yml +1 -1
- package/package.json +10 -9
- package/script/test +1 -1
- package/src/nextbus.js +38 -15
- package/test/fixtures/agencies.json +3 -3
- package/test/fixtures/stops-BRO12WN-next.json +726 -1227
- package/test/fixtures/stops-PORGRESF-next.json +188 -30
- package/test/fixtures/stops-near-gps.json +29 -29
- package/test/helpers/hubot-helper.js +77 -0
- package/test/nextbus-slack.test.js +88 -103
- package/test/nextbus.test.js +114 -160
- package/vitest.config.mjs +14 -0
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.4.0",
|
|
5
5
|
"author": "Stephen Yeargin <stephen@yearg.in>",
|
|
6
6
|
"homepage": "https://github.com/transitnownash/hubot-nextbus",
|
|
7
7
|
"license": "MIT",
|
|
@@ -24,18 +24,16 @@
|
|
|
24
24
|
"hubot": ">=3 || 0.0.0-development"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"
|
|
27
|
+
"@vitest/coverage-v8": "^4.1.6",
|
|
28
28
|
"eslint": "^8.56.0",
|
|
29
29
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
30
30
|
"eslint-plugin-import": "^2.29.1",
|
|
31
31
|
"eslint-plugin-n": "^16.5.0",
|
|
32
32
|
"eslint-plugin-promise": "^6.1.1",
|
|
33
|
-
"hubot
|
|
34
|
-
"husky": "^
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"sinon": "^17.0.1",
|
|
38
|
-
"sinon-chai": "^3.7.0"
|
|
33
|
+
"hubot": "^14.1.0",
|
|
34
|
+
"husky": "^9.1.7",
|
|
35
|
+
"nock": "^14.0.17",
|
|
36
|
+
"vitest": "^4.1.6"
|
|
39
37
|
},
|
|
40
38
|
"main": "index.js",
|
|
41
39
|
"scripts": {
|
|
@@ -46,7 +44,10 @@
|
|
|
46
44
|
"ascii-table": "^0.0.9",
|
|
47
45
|
"moment": "^2.29.4"
|
|
48
46
|
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22"
|
|
49
|
+
},
|
|
49
50
|
"volta": {
|
|
50
|
-
"node": "
|
|
51
|
+
"node": "22.22.3"
|
|
51
52
|
}
|
|
52
53
|
}
|
package/script/test
CHANGED
package/src/nextbus.js
CHANGED
|
@@ -27,11 +27,12 @@ module.exports = (robot) => {
|
|
|
27
27
|
robot.logger.debug(url);
|
|
28
28
|
robot.http(url)
|
|
29
29
|
.get()((err, res, body) => {
|
|
30
|
-
const response = JSON.parse(body);
|
|
31
30
|
if (err) {
|
|
32
|
-
|
|
31
|
+
const detail = err.errors ? err.errors.map((e) => e.message).join(', ') : err.message;
|
|
32
|
+
msg.send(`Error fetching NextBus data: ${detail || err}`);
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
+
const response = JSON.parse(body);
|
|
35
36
|
if (response.error) {
|
|
36
37
|
msg.send(response.error);
|
|
37
38
|
return;
|
|
@@ -84,6 +85,24 @@ module.exports = (robot) => {
|
|
|
84
85
|
return alertLines.join('\n');
|
|
85
86
|
};
|
|
86
87
|
|
|
88
|
+
const tripTableHeading = ['Time', 'Route', 'ETA'];
|
|
89
|
+
|
|
90
|
+
const formatSlackTripTable = (heading, rows) => ({
|
|
91
|
+
blocks: [
|
|
92
|
+
{
|
|
93
|
+
type: 'header',
|
|
94
|
+
text: { type: 'plain_text', text: heading, emoji: true },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'table',
|
|
98
|
+
rows: [tripTableHeading, ...rows].map((row) => row.map((cell) => ({
|
|
99
|
+
type: 'raw_text',
|
|
100
|
+
text: String(cell ?? ''),
|
|
101
|
+
}))),
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
|
|
87
106
|
const queryStopById = (stopId, msg) => getAPIResponse('agencies.json', msg, (agencies) => {
|
|
88
107
|
// Override timezone for moment() calls
|
|
89
108
|
process.env.TZ = agencies.data[0].agency_timezone;
|
|
@@ -118,31 +137,31 @@ module.exports = (robot) => {
|
|
|
118
137
|
return;
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
const
|
|
122
|
-
nextTripsData.slice(0, 5).forEach((tripData) => {
|
|
140
|
+
const rows = nextTripsData.slice(0, 5).map((tripData) => {
|
|
123
141
|
const tripTime = formatTripTimeAsMoment(tripData.stop_time.arrival_time);
|
|
124
142
|
const realtimeStatus = getRealTimeStatus(tripData.stop_time);
|
|
125
143
|
const hasVehicle = vehiclePositions
|
|
126
144
|
&& vehiclePositions.some((vp) => vp.trip && vp.trip.trip_id === tripData.trip.trip_gid);
|
|
127
145
|
const busIndicator = hasVehicle ? ' 🚌' : '';
|
|
128
146
|
const timeUntilText = realtimeStatus ? `${tripTime.fromNow()} (${realtimeStatus})` : tripTime.fromNow();
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
return [
|
|
148
|
+
tripTime.format('LT'),
|
|
131
149
|
`#${tripData.trip.route_gid} - ${tripData.trip.trip_headsign}${busIndicator}`,
|
|
132
150
|
timeUntilText,
|
|
133
151
|
];
|
|
134
|
-
table.addRow(columns);
|
|
135
152
|
});
|
|
136
153
|
const adapterName = robot.adapterName ?? robot.adapter?.name ?? '';
|
|
137
|
-
table.removeBorder();
|
|
138
|
-
const tableOutput = table.toString().split('\n').map((line) => line.trimEnd()).join('\n');
|
|
139
|
-
|
|
140
|
-
const heading = `🚏 *${stop.stop_name}*`;
|
|
141
154
|
|
|
142
155
|
if (/slack/i.test(adapterName)) {
|
|
143
|
-
msg.send(
|
|
156
|
+
msg.send(formatSlackTripTable(`🚏 ${stop.stop_name}`, rows));
|
|
144
157
|
return;
|
|
145
158
|
}
|
|
159
|
+
|
|
160
|
+
const table = new AsciiTable();
|
|
161
|
+
rows.forEach((row) => table.addRow(row));
|
|
162
|
+
table.removeBorder();
|
|
163
|
+
const tableOutput = table.toString().split('\n').map((line) => line.trimEnd()).join('\n');
|
|
164
|
+
const heading = `🚏 *${stop.stop_name}*`;
|
|
146
165
|
msg.send(`${heading}\n${tableOutput}`);
|
|
147
166
|
});
|
|
148
167
|
});
|
|
@@ -156,7 +175,7 @@ module.exports = (robot) => {
|
|
|
156
175
|
|
|
157
176
|
getAPIResponse(`stops/near/${latlon}/1000.json?per_page=5`, msg, (stops) => {
|
|
158
177
|
if (stops.total > 0) {
|
|
159
|
-
queryStopById(stops.data[0].
|
|
178
|
+
queryStopById(stops.data[0].stop_code, msg);
|
|
160
179
|
return;
|
|
161
180
|
}
|
|
162
181
|
msg.send(`No stops found near ${latlon}`);
|
|
@@ -165,16 +184,20 @@ module.exports = (robot) => {
|
|
|
165
184
|
|
|
166
185
|
// get a list of nearby stops
|
|
167
186
|
robot.respond(/(?:bus|nextbus) stops$/i, (msg) => getAPIResponse(`stops/near/${latlon}/1000.json?per_page=5`, msg, (stops) => {
|
|
187
|
+
if (stops.total === 0) {
|
|
188
|
+
msg.send(`No stops found near ${latlon}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
168
191
|
msg.send('List of nearby stops:');
|
|
169
192
|
const output = [];
|
|
170
193
|
stops.data.forEach((stop) => {
|
|
171
|
-
output.push(`-
|
|
194
|
+
output.push(`- \`#${stop.stop_code}\` - ${stop.stop_name}`);
|
|
172
195
|
});
|
|
173
196
|
msg.send(output.join('\n'));
|
|
174
197
|
}));
|
|
175
198
|
|
|
176
199
|
// get a particular stop's next bus
|
|
177
|
-
robot.respond(/(?:bus|nextbus) stop ([A-Z0-9_]+)$/i, (msg) => {
|
|
200
|
+
robot.respond(/(?:bus|nextbus) stop #?([A-Z0-9_]+)$/i, (msg) => {
|
|
178
201
|
const stopId = msg.match[1];
|
|
179
202
|
robot.logger.debug(stopId);
|
|
180
203
|
queryStopById(stopId, msg);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"per_page": 100,
|
|
5
5
|
"data": [
|
|
6
6
|
{
|
|
7
|
-
"id":
|
|
7
|
+
"id": 178,
|
|
8
8
|
"agency_gid": "WeGo Public Transit",
|
|
9
9
|
"agency_name": "WeGo Public Transit",
|
|
10
10
|
"agency_url": "http://www.wegotransit.com",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"agency_phone": "615-862-8833",
|
|
14
14
|
"agency_fare_url": null,
|
|
15
15
|
"agency_email": null,
|
|
16
|
-
"created_at": "
|
|
17
|
-
"updated_at": "
|
|
16
|
+
"created_at": "2026-08-30T04:05:14.056-05:00",
|
|
17
|
+
"updated_at": "2026-08-30T04:05:14.056-05:00"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
}
|