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.
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "npm"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ ignore:
8
+ - dependency-name: "eslint*"
@@ -17,7 +17,7 @@ jobs:
17
17
 
18
18
  - uses: actions/setup-node@v6
19
19
  with:
20
- node-version: "20"
20
+ node-version: "24"
21
21
  registry-url: https://registry.npmjs.org
22
22
 
23
23
  - name: Update npm to latest version
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.3.0",
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
- "chai": "^4.3.10",
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-test-helper": "^1.9.0",
34
- "husky": "^8.0.3",
35
- "mocha": "^10.2.0",
36
- "nock": "^13.4.0",
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": "18.19.0"
51
+ "node": "22.22.3"
51
52
  }
52
53
  }
package/script/test CHANGED
@@ -3,4 +3,4 @@
3
3
  # bootstrap environment
4
4
  source script/bootstrap
5
5
 
6
- mocha "test/**/*.js" --reporter spec --exit
6
+ vitest run --coverage
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
- msg.send(err);
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 table = new AsciiTable();
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
- const columns = [
130
- formatTripTimeAsMoment(tripData.stop_time.arrival_time).format('LT'),
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(`${heading}\n\`\`\`\n${tableOutput}\n\`\`\``);
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].stop_gid, msg);
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(`- [${stop.stop_gid}] ${stop.stop_name}`);
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": 1,
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": "2022-08-02T21:14:03.545-05:00",
17
- "updated_at": "2022-08-02T21:14:03.545-05:00"
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
  }