mirror of
https://github.com/SrIzan10/flight-slack.git
synced 2026-06-06 00:56:52 +00:00
feat: flight updates!!!!
This commit is contained in:
61
prisma/migrations/20250714203953_channel_ids/migration.sql
Normal file
61
prisma/migrations/20250714203953_channel_ids/migration.sql
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `channelId` to the `Flight` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Flight" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"channelId" TEXT NOT NULL,
|
||||
"faFlightId" TEXT NOT NULL,
|
||||
"ident" TEXT NOT NULL,
|
||||
"identIcao" TEXT NOT NULL,
|
||||
"identIata" TEXT,
|
||||
"registration" TEXT,
|
||||
"aircraftType" TEXT,
|
||||
"originIcao" TEXT NOT NULL,
|
||||
"originIata" TEXT NOT NULL,
|
||||
"originName" TEXT NOT NULL,
|
||||
"originCity" TEXT NOT NULL,
|
||||
"destinationIcao" TEXT NOT NULL,
|
||||
"destinationIata" TEXT NOT NULL,
|
||||
"destinationName" TEXT NOT NULL,
|
||||
"destinationCity" TEXT NOT NULL,
|
||||
"scheduledOut" DATETIME NOT NULL,
|
||||
"scheduledOff" DATETIME NOT NULL,
|
||||
"scheduledOn" DATETIME NOT NULL,
|
||||
"scheduledIn" DATETIME NOT NULL,
|
||||
"estimatedOut" DATETIME,
|
||||
"estimatedOff" DATETIME,
|
||||
"estimatedOn" DATETIME,
|
||||
"estimatedIn" DATETIME,
|
||||
"actualOut" DATETIME,
|
||||
"actualOff" DATETIME,
|
||||
"actualOn" DATETIME,
|
||||
"actualIn" DATETIME,
|
||||
"status" TEXT NOT NULL,
|
||||
"departureDelay" INTEGER,
|
||||
"arrivalDelay" INTEGER,
|
||||
"cancelled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"diverted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"progressPercent" INTEGER,
|
||||
"gateOrigin" TEXT,
|
||||
"gateDestination" TEXT,
|
||||
"terminalOrigin" TEXT,
|
||||
"terminalDestination" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Flight" ("actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationIcao", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originIcao", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId") SELECT "actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationIcao", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originIcao", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId" FROM "Flight";
|
||||
DROP TABLE "Flight";
|
||||
ALTER TABLE "new_Flight" RENAME TO "Flight";
|
||||
CREATE UNIQUE INDEX "Flight_faFlightId_key" ON "Flight"("faFlightId");
|
||||
CREATE INDEX "Flight_userId_idx" ON "Flight"("userId");
|
||||
CREATE INDEX "Flight_faFlightId_idx" ON "Flight"("faFlightId");
|
||||
CREATE INDEX "Flight_scheduledOff_idx" ON "Flight"("scheduledOff");
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -0,0 +1,55 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Flight" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"channelId" TEXT NOT NULL,
|
||||
"faFlightId" TEXT NOT NULL,
|
||||
"ident" TEXT NOT NULL,
|
||||
"identIcao" TEXT NOT NULL,
|
||||
"identIata" TEXT,
|
||||
"registration" TEXT,
|
||||
"aircraftType" TEXT,
|
||||
"originIcao" TEXT NOT NULL,
|
||||
"originIata" TEXT NOT NULL,
|
||||
"originName" TEXT NOT NULL,
|
||||
"originCity" TEXT NOT NULL,
|
||||
"destinationIcao" TEXT NOT NULL,
|
||||
"destinationIata" TEXT NOT NULL,
|
||||
"destinationName" TEXT NOT NULL,
|
||||
"destinationCity" TEXT NOT NULL,
|
||||
"scheduledOut" DATETIME,
|
||||
"scheduledOff" DATETIME NOT NULL,
|
||||
"scheduledOn" DATETIME NOT NULL,
|
||||
"scheduledIn" DATETIME NOT NULL,
|
||||
"estimatedOut" DATETIME,
|
||||
"estimatedOff" DATETIME,
|
||||
"estimatedOn" DATETIME,
|
||||
"estimatedIn" DATETIME,
|
||||
"actualOut" DATETIME,
|
||||
"actualOff" DATETIME,
|
||||
"actualOn" DATETIME,
|
||||
"actualIn" DATETIME,
|
||||
"status" TEXT NOT NULL,
|
||||
"departureDelay" INTEGER,
|
||||
"arrivalDelay" INTEGER,
|
||||
"cancelled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"diverted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"progressPercent" INTEGER,
|
||||
"gateOrigin" TEXT,
|
||||
"gateDestination" TEXT,
|
||||
"terminalOrigin" TEXT,
|
||||
"terminalDestination" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Flight" ("actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "channelId", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationIcao", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originIcao", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId") SELECT "actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "channelId", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationIcao", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originIcao", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId" FROM "Flight";
|
||||
DROP TABLE "Flight";
|
||||
ALTER TABLE "new_Flight" RENAME TO "Flight";
|
||||
CREATE UNIQUE INDEX "Flight_faFlightId_key" ON "Flight"("faFlightId");
|
||||
CREATE INDEX "Flight_userId_idx" ON "Flight"("userId");
|
||||
CREATE INDEX "Flight_faFlightId_idx" ON "Flight"("faFlightId");
|
||||
CREATE INDEX "Flight_scheduledOff_idx" ON "Flight"("scheduledOff");
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -0,0 +1,55 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Flight" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"channelId" TEXT NOT NULL,
|
||||
"faFlightId" TEXT NOT NULL,
|
||||
"ident" TEXT NOT NULL,
|
||||
"identIcao" TEXT NOT NULL,
|
||||
"identIata" TEXT,
|
||||
"registration" TEXT,
|
||||
"aircraftType" TEXT,
|
||||
"originIcao" TEXT NOT NULL,
|
||||
"originIata" TEXT NOT NULL,
|
||||
"originName" TEXT NOT NULL,
|
||||
"originCity" TEXT NOT NULL,
|
||||
"destinationIcao" TEXT NOT NULL,
|
||||
"destinationIata" TEXT NOT NULL,
|
||||
"destinationName" TEXT NOT NULL,
|
||||
"destinationCity" TEXT NOT NULL,
|
||||
"scheduledOut" DATETIME,
|
||||
"scheduledOff" DATETIME NOT NULL,
|
||||
"scheduledOn" DATETIME NOT NULL,
|
||||
"scheduledIn" DATETIME,
|
||||
"estimatedOut" DATETIME,
|
||||
"estimatedOff" DATETIME,
|
||||
"estimatedOn" DATETIME,
|
||||
"estimatedIn" DATETIME,
|
||||
"actualOut" DATETIME,
|
||||
"actualOff" DATETIME,
|
||||
"actualOn" DATETIME,
|
||||
"actualIn" DATETIME,
|
||||
"status" TEXT NOT NULL,
|
||||
"departureDelay" INTEGER,
|
||||
"arrivalDelay" INTEGER,
|
||||
"cancelled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"diverted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"progressPercent" INTEGER,
|
||||
"gateOrigin" TEXT,
|
||||
"gateDestination" TEXT,
|
||||
"terminalOrigin" TEXT,
|
||||
"terminalDestination" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Flight" ("actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "channelId", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationIcao", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originIcao", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId") SELECT "actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "channelId", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationIcao", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originIcao", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId" FROM "Flight";
|
||||
DROP TABLE "Flight";
|
||||
ALTER TABLE "new_Flight" RENAME TO "Flight";
|
||||
CREATE UNIQUE INDEX "Flight_faFlightId_key" ON "Flight"("faFlightId");
|
||||
CREATE INDEX "Flight_userId_idx" ON "Flight"("userId");
|
||||
CREATE INDEX "Flight_faFlightId_idx" ON "Flight"("faFlightId");
|
||||
CREATE INDEX "Flight_scheduledOff_idx" ON "Flight"("scheduledOff");
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -13,6 +13,7 @@ datasource db {
|
||||
model Flight {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
channelId String
|
||||
|
||||
// identifiers
|
||||
faFlightId String @unique // fa_flight_id
|
||||
@@ -36,10 +37,10 @@ model Flight {
|
||||
destinationCity String // city name
|
||||
|
||||
// timing (stored in iso 8601 format on the api)
|
||||
scheduledOut DateTime
|
||||
scheduledOut DateTime?
|
||||
scheduledOff DateTime // takeoff time
|
||||
scheduledOn DateTime // landing time
|
||||
scheduledIn DateTime
|
||||
scheduledIn DateTime?
|
||||
|
||||
estimatedOut DateTime?
|
||||
estimatedOff DateTime?
|
||||
|
||||
16
src/index.ts
16
src/index.ts
@@ -6,8 +6,9 @@ import { FlightAware, type ScheduledDeparture } from './util/flightAware';
|
||||
import { parseDate, formatDate } from './util/dates';
|
||||
import { Airports } from './util/airports';
|
||||
import { AdsBDB } from './util/adsbdb';
|
||||
import { FlightUpdater } from './util/flightUpdater';
|
||||
|
||||
const app = new App({
|
||||
export const app = new App({
|
||||
token: process.env.SLACK_BOT_TOKEN,
|
||||
socketMode: true,
|
||||
appToken: process.env.SLACK_APP_TOKEN,
|
||||
@@ -19,11 +20,20 @@ export const cache = new BunCache();
|
||||
export const openSky = new OpenskyService();
|
||||
export const flightAware = new FlightAware();
|
||||
export const adsbDb = new AdsBDB();
|
||||
export const flightUpdater = new FlightUpdater();
|
||||
|
||||
app.command('/flight-add', async ({ command, ack, respond }) => {
|
||||
const EXAMPLE = `_Example: \`/flight-add AGP today 11\` looks for flights from AGP today at 11:00 (24-hour UTC)._`;
|
||||
await ack();
|
||||
|
||||
if (!command.channel_id || (!command.channel_id.startsWith('C') && !command.channel_id.startsWith('G'))) {
|
||||
await respond({
|
||||
text: '❌ This command can only be used in channels.',
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = command.text.split(' ');
|
||||
let [airportCode, dateInput, hourInput] = parts;
|
||||
|
||||
@@ -206,6 +216,7 @@ app.action('flight_selection', async ({ body, ack, respond }) => {
|
||||
await db.flight.create({
|
||||
data: {
|
||||
userId: body.user.id,
|
||||
channelId: body.channel?.id!,
|
||||
faFlightId: flightData.fa_flight_id,
|
||||
ident: flightData.ident,
|
||||
identIcao: flightData.ident_icao,
|
||||
@@ -250,7 +261,7 @@ app.action('flight_selection', async ({ body, ack, respond }) => {
|
||||
replace_original: true,
|
||||
});
|
||||
await app.client.chat.postMessage({
|
||||
channel: body.user.id,
|
||||
channel: body.channel?.id!,
|
||||
text: `Flight \`${callsign}\` is now being tracked inside this channel!`,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -296,4 +307,5 @@ app.action('flight_page_next', async ({ body, ack, respond }) => {
|
||||
});
|
||||
|
||||
await app.start();
|
||||
flightUpdater.start();
|
||||
console.log("We're up and running :)");
|
||||
|
||||
@@ -99,10 +99,10 @@ export interface ScheduledDeparture {
|
||||
departure_delay: number | null;
|
||||
arrival_delay: number | null;
|
||||
filed_ete: number;
|
||||
scheduled_out: string;
|
||||
scheduled_out?: string;
|
||||
estimated_out: string | null;
|
||||
actual_out: string | null;
|
||||
scheduled_off: string;
|
||||
scheduled_off?: string;
|
||||
estimated_off: string | null;
|
||||
actual_off: string | null;
|
||||
scheduled_on: string;
|
||||
|
||||
245
src/util/flightUpdater.ts
Normal file
245
src/util/flightUpdater.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import type { Flight } from '@prisma/client';
|
||||
import { db, flightAware, app } from '../index';
|
||||
|
||||
export class FlightUpdater {
|
||||
private updateInterval: NodeJS.Timeout | null = null;
|
||||
private flightIntervals: Map<string, NodeJS.Timeout> = new Map();
|
||||
|
||||
public start() {
|
||||
if (this.updateInterval) return;
|
||||
|
||||
console.log('flight updater starting');
|
||||
|
||||
this.updateInterval = setInterval(() => this.manageFlightPolling(), 10 * 60 * 1000);
|
||||
this.manageFlightPolling();
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.updateInterval) {
|
||||
clearInterval(this.updateInterval);
|
||||
this.updateInterval = null;
|
||||
}
|
||||
|
||||
this.flightIntervals.forEach(interval => clearInterval(interval));
|
||||
this.flightIntervals.clear();
|
||||
}
|
||||
|
||||
private async manageFlightPolling() {
|
||||
try {
|
||||
const flights = await db.flight.findMany({
|
||||
where: {
|
||||
actualIn: null,
|
||||
cancelled: false,
|
||||
scheduledOff: {
|
||||
gte: new Date(Date.now() - 4 * 60 * 60 * 1000),
|
||||
lte: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const currentlyTracked = new Set(this.flightIntervals.keys());
|
||||
const activeFlightIds = new Set(flights.map(f => f.id));
|
||||
|
||||
// remove intervals for flights no longer active
|
||||
for (const flightId of currentlyTracked) {
|
||||
if (!activeFlightIds.has(flightId)) {
|
||||
clearInterval(this.flightIntervals.get(flightId)!);
|
||||
this.flightIntervals.delete(flightId);
|
||||
console.log(`Stopped tracking flight ${flightId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// add intervals for new flights
|
||||
for (const flight of flights) {
|
||||
if (!this.flightIntervals.has(flight.id)) {
|
||||
this.setupFlightPolling(flight);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Managing ${this.flightIntervals.size} flight polling intervals`);
|
||||
} catch (error) {
|
||||
console.error('Error managing flight polling:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private setupFlightPolling(flight: Flight) {
|
||||
const pollInterval = this.calculatePollInterval(flight);
|
||||
|
||||
console.log(`Setting up polling for flight ${flight.ident} every ${pollInterval / 60000} minutes`);
|
||||
|
||||
// initial update
|
||||
this.updateSingleFlight(flight);
|
||||
|
||||
// set up interval
|
||||
const interval = setInterval(() => this.updateSingleFlight(flight), pollInterval);
|
||||
this.flightIntervals.set(flight.id, interval);
|
||||
}
|
||||
|
||||
private calculatePollInterval(flight: Flight): number {
|
||||
const now = new Date();
|
||||
const scheduledOff = new Date(flight.scheduledOff);
|
||||
const scheduledOn = new Date(flight.scheduledOn);
|
||||
|
||||
// pre-departure phase (more than 2 hours before takeoff)
|
||||
if (now < new Date(scheduledOff.getTime() - 2 * 60 * 60 * 1000)) {
|
||||
return 30 * 60 * 1000; // 30 minutes
|
||||
}
|
||||
|
||||
// pre-departure phase (less than 2 hours before takeoff)
|
||||
if (now < scheduledOff) {
|
||||
return 5 * 60 * 1000; // 5 minutes
|
||||
}
|
||||
|
||||
// In-flight phase
|
||||
if (!flight.actualOn && now < new Date(scheduledOn.getTime() + 2 * 60 * 60 * 1000)) {
|
||||
// frequent updates during takeoff and initial 5 minute climb
|
||||
if (now < new Date(scheduledOff.getTime() + 5 * 60 * 1000)) {
|
||||
return 2 * 60 * 1000; // 2 minutes
|
||||
}
|
||||
|
||||
// Medium frequency during cruise
|
||||
const flightDuration = scheduledOn.getTime() - scheduledOff.getTime();
|
||||
const timeInFlight = now.getTime() - scheduledOff.getTime();
|
||||
const progressPercent = (timeInFlight / flightDuration) * 100;
|
||||
|
||||
// frequent updates during landing
|
||||
if (progressPercent > 80) {
|
||||
return 3 * 60 * 1000; // 3 minutes
|
||||
}
|
||||
|
||||
return 8 * 60 * 1000; // 8 minutes during cruise
|
||||
}
|
||||
|
||||
// flight arrived
|
||||
return 15 * 60 * 1000;
|
||||
}
|
||||
|
||||
private async updateSingleFlight(flight: Flight) {
|
||||
console.log(`updating flight ${flight.ident} (${flight.id})`);
|
||||
try {
|
||||
if (flight.actualIn) {
|
||||
this.removeFlightPolling(flight.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const flightInfo = await flightAware.getFlightInfo(flight.faFlightId);
|
||||
const newData = flightInfo.flights[0];
|
||||
|
||||
if (!newData) return;
|
||||
|
||||
const changes = [];
|
||||
|
||||
if (flight.status !== newData.status) {
|
||||
changes.push(`Status: ${newData.status}`);
|
||||
}
|
||||
|
||||
if (Math.abs((flight.departureDelay || 0) - (newData.departure_delay || 0)) > 10) {
|
||||
changes.push(`Departure delay: ${newData.departure_delay || 0} minutes`);
|
||||
}
|
||||
|
||||
if (flight.gateOrigin !== newData.gate_origin && newData.gate_origin) {
|
||||
changes.push(`Gate changed to: ${newData.gate_origin}`);
|
||||
}
|
||||
|
||||
if (!flight.actualOff && newData.actual_off) {
|
||||
changes.push('✈️ Flight has taken off!');
|
||||
this.adjustFlightPolling(flight.id, flight);
|
||||
}
|
||||
|
||||
if (!flight.actualOn && newData.actual_on) {
|
||||
changes.push('🛬 Flight has landed!');
|
||||
this.removeFlightPolling(flight.id);
|
||||
}
|
||||
|
||||
if (!flight.cancelled && newData.cancelled) {
|
||||
changes.push('❌ Flight cancelled');
|
||||
this.removeFlightPolling(flight.id);
|
||||
}
|
||||
|
||||
if (!flight.diverted && newData.diverted) {
|
||||
changes.push(`🚨 Flight diverted to ${newData.destination.code_iata} (${newData.destination.name})`);
|
||||
}
|
||||
|
||||
// smart progress updates (0 - 10, 10 - 20, etc.)
|
||||
if (flight.progressPercent !== null && newData.progress_percent !== null && newData.actual_off && !newData.actual_on) {
|
||||
const oldTens = Math.floor((flight.progressPercent || 0) / 10);
|
||||
const newTens = Math.floor(newData.progress_percent / 10);
|
||||
|
||||
if (oldTens !== newTens && newData.progress_percent !== 100) {
|
||||
changes.push(`🔄 Flight progress: ${newData.progress_percent}%\n${this.slackProgressbar(newData.progress_percent)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedFlight = await db.flight.update({
|
||||
where: { id: flight.id },
|
||||
data: {
|
||||
status: newData.status,
|
||||
departureDelay: newData.departure_delay,
|
||||
arrivalDelay: newData.arrival_delay,
|
||||
actualOut: newData.actual_out,
|
||||
actualOff: newData.actual_off,
|
||||
actualOn: newData.actual_on,
|
||||
actualIn: newData.actual_in,
|
||||
cancelled: newData.cancelled,
|
||||
diverted: newData.diverted,
|
||||
gateOrigin: newData.gate_origin,
|
||||
progressPercent: newData.progress_percent,
|
||||
}
|
||||
});
|
||||
|
||||
// stop polling on completion
|
||||
if (updatedFlight.actualIn) {
|
||||
this.removeFlightPolling(flight.id);
|
||||
}
|
||||
|
||||
if (changes.length > 0) {
|
||||
const message = `🔔 *${flight.ident}* (${flight.originIata} → ${flight.destinationIata})\n${changes.join('\n')}`;
|
||||
|
||||
await app.client.chat.postMessage({
|
||||
channel: flight.channelId,
|
||||
text: message
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`error updating flight ${flight.ident}:`, error);
|
||||
|
||||
// adjust polling for error
|
||||
this.adjustFlightPolling(flight.id, flight, true);
|
||||
}
|
||||
}
|
||||
|
||||
private adjustFlightPolling(flightId: string, flight: Flight, isError: boolean = false) {
|
||||
// clear current interval
|
||||
const existingInterval = this.flightIntervals.get(flightId);
|
||||
if (existingInterval) {
|
||||
clearInterval(existingInterval);
|
||||
}
|
||||
|
||||
// calculate interval
|
||||
let newInterval = this.calculatePollInterval(flight);
|
||||
|
||||
// increase interval in case of errors
|
||||
if (isError) {
|
||||
newInterval *= 2;
|
||||
}
|
||||
|
||||
// update interval
|
||||
const interval = setInterval(() => this.updateSingleFlight(flight), newInterval);
|
||||
this.flightIntervals.set(flightId, interval);
|
||||
}
|
||||
|
||||
private removeFlightPolling(flightId: string) {
|
||||
const interval = this.flightIntervals.get(flightId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
this.flightIntervals.delete(flightId);
|
||||
console.log(`Stopped polling completed flight ${flightId}`);
|
||||
}
|
||||
}
|
||||
|
||||
private slackProgressbar(percent: number): string {
|
||||
const filled = Math.round(percent / 10);
|
||||
const empty = 10 - filled;
|
||||
return `\`${'█'.repeat(filled)}${'░'.repeat(empty)}\` ${percent}%`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user