hubot-nextbus 2.2.2 → 2.3.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.
@@ -10,7 +10,7 @@ jobs:
10
10
  strategy:
11
11
  fail-fast: false
12
12
  matrix:
13
- node-version: [16.x, 18.x, 20.x]
13
+ node-version: [20.x, 22.x, 24.x]
14
14
 
15
15
  steps:
16
16
  - uses: actions/checkout@v3
@@ -32,4 +32,3 @@ jobs:
32
32
  npm test
33
33
  env:
34
34
  CI: true
35
-
@@ -1,26 +1,51 @@
1
1
  on:
2
2
  push:
3
- branches: main
3
+ branches:
4
+ - main
4
5
 
5
6
  name: npm-publish
6
7
 
7
8
  permissions:
8
9
  contents: write
10
+ id-token: write
9
11
 
10
12
  jobs:
11
13
  publish:
12
14
  runs-on: ubuntu-latest
13
15
  steps:
14
- - uses: actions/checkout@v4
15
- - uses: actions/setup-node@v3
16
+ - uses: actions/checkout@v6
17
+
18
+ - uses: actions/setup-node@v6
16
19
  with:
17
20
  node-version: "20"
21
+ registry-url: https://registry.npmjs.org
22
+
23
+ - name: Update npm to latest version
24
+ run: npm install -g npm@latest
25
+
18
26
  - run: npm ci
19
27
  - run: npm test
20
- - uses: JS-DevTools/npm-publish@v3
28
+
29
+ - name: Publish to npm
21
30
  id: publish
22
- with:
23
- token: ${{ secrets.NPM_AUTH_TOKEN }}
31
+ run: |
32
+ # Verify npm version supports OIDC
33
+ echo "npm version: $(npm --version)"
34
+
35
+ # Check if version is already published
36
+ PACKAGE_NAME=$(node -p "require('./package.json').name")
37
+ PACKAGE_VERSION=$(node -p "require('./package.json').version")
38
+
39
+ if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version 2>/dev/null; then
40
+ echo "Version $PACKAGE_VERSION already published"
41
+ echo "type=" >> $GITHUB_OUTPUT
42
+ else
43
+ echo "Publishing $PACKAGE_NAME@$PACKAGE_VERSION..."
44
+ npm publish --access public
45
+ echo "type=patch" >> $GITHUB_OUTPUT
46
+ echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
47
+ fi
48
+
24
49
  - if: ${{ steps.publish.outputs.type }}
25
50
  name: Create Release
26
51
  env:
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.2.2",
4
+ "version": "2.3.0",
5
5
  "author": "Stephen Yeargin <stephen@yearg.in>",
6
6
  "homepage": "https://github.com/transitnownash/hubot-nextbus",
7
7
  "license": "MIT",
package/src/nextbus.js CHANGED
@@ -53,33 +53,97 @@ module.exports = (robot) => {
53
53
  return moment(`${moment().format('YYYY-MM-DD')} ${timeStr.trim().padStart(8, '0')}`);
54
54
  };
55
55
 
56
+ const getRealTimeStatus = (stopTime) => {
57
+ // Check if realtime data is available
58
+ if (!stopTime.realtime || !stopTime.realtime.departure) {
59
+ return '';
60
+ }
61
+
62
+ const scheduled = formatTripTimeAsMoment(stopTime.departure_time);
63
+ const actual = formatTripTimeAsMoment(stopTime.realtime.departure);
64
+ const diffMinutes = actual.diff(scheduled, 'minutes');
65
+
66
+ if (diffMinutes === 0) {
67
+ return 'On time';
68
+ } if (diffMinutes > 0) {
69
+ return `${diffMinutes}m late`;
70
+ }
71
+ return `${Math.abs(diffMinutes)}m early`;
72
+ };
73
+
74
+ const formatAlerts = (alerts) => {
75
+ if (!alerts || alerts.length === 0) {
76
+ return '';
77
+ }
78
+
79
+ const alertLines = alerts.map((alert) => {
80
+ const headerText = alert.header_text?.translation?.[0]?.text || 'Service Alert';
81
+ return `⚠️ *${headerText.trim()}*`;
82
+ });
83
+
84
+ return alertLines.join('\n');
85
+ };
86
+
56
87
  const queryStopById = (stopId, msg) => getAPIResponse('agencies.json', msg, (agencies) => {
57
88
  // Override timezone for moment() calls
58
89
  process.env.TZ = agencies.data[0].agency_timezone;
59
90
  robot.logger.debug(process.env.TZ);
60
91
  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);
92
+ getAPIResponse(`stops/${stopId}/next.json`, msg, (response) => {
93
+ const {
94
+ stop,
95
+ next_trip: nextTrip,
96
+ upcoming_trips: upcomingTrips,
97
+ alerts,
98
+ vehicle_positions: vehiclePositions,
99
+ } = response;
100
+
101
+ // Show alerts if any exist
102
+ const alertMessage = formatAlerts(alerts);
103
+ if (alertMessage) {
104
+ msg.send(alertMessage);
105
+ }
106
+
107
+ // Combine next trip with upcoming trips to get all trips
108
+ const allTrips = nextTrip ? [nextTrip, ...upcomingTrips] : upcomingTrips;
109
+
110
+ const nextTripsData = allTrips.filter((tripData) => {
111
+ const tripTime = formatTripTimeAsMoment(tripData.stop_time.arrival_time);
64
112
  return tripTime.isAfter(moment(), 'second');
65
113
  });
66
- robot.logger.debug(nextTrips);
67
- if (nextTrips.length === 0) {
114
+
115
+ robot.logger.debug(nextTripsData);
116
+ if (nextTripsData.length === 0) {
68
117
  msg.send('The last bus has already run for today.');
69
118
  return;
70
119
  }
71
120
 
72
121
  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()]);
122
+ nextTripsData.slice(0, 5).forEach((tripData) => {
123
+ const tripTime = formatTripTimeAsMoment(tripData.stop_time.arrival_time);
124
+ const realtimeStatus = getRealTimeStatus(tripData.stop_time);
125
+ const hasVehicle = vehiclePositions
126
+ && vehiclePositions.some((vp) => vp.trip && vp.trip.trip_id === tripData.trip.trip_gid);
127
+ const busIndicator = hasVehicle ? ' 🚌' : '';
128
+ const timeUntilText = realtimeStatus ? `${tripTime.fromNow()} (${realtimeStatus})` : tripTime.fromNow();
129
+ const columns = [
130
+ formatTripTimeAsMoment(tripData.stop_time.arrival_time).format('LT'),
131
+ `#${tripData.trip.route_gid} - ${tripData.trip.trip_headsign}${busIndicator}`,
132
+ timeUntilText,
133
+ ];
134
+ table.addRow(columns);
80
135
  });
136
+ const adapterName = robot.adapterName ?? robot.adapter?.name ?? '';
81
137
  table.removeBorder();
82
- msg.send(table.toString());
138
+ const tableOutput = table.toString().split('\n').map((line) => line.trimEnd()).join('\n');
139
+
140
+ const heading = `🚏 *${stop.stop_name}*`;
141
+
142
+ if (/slack/i.test(adapterName)) {
143
+ msg.send(`${heading}\n\`\`\`\n${tableOutput}\n\`\`\``);
144
+ return;
145
+ }
146
+ msg.send(`${heading}\n${tableOutput}`);
83
147
  });
84
148
  });
85
149
 
@@ -0,0 +1,5 @@
1
+ // Description
2
+ // Mock Slack adapter
3
+ module.exports = (robot) => {
4
+ robot.adapterName = 'slack';
5
+ };