From aacae797698b43de02ffce57a384a2643d036db0 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Fri, 10 Oct 2025 12:48:45 +0200 Subject: [PATCH] Update --- src/utils/notificationService.ts | 102 ------------- src/utils/webhookService.ts | 236 ------------------------------- 2 files changed, 338 deletions(-) delete mode 100644 src/utils/notificationService.ts delete mode 100644 src/utils/webhookService.ts diff --git a/src/utils/notificationService.ts b/src/utils/notificationService.ts deleted file mode 100644 index 3d2af1d..0000000 --- a/src/utils/notificationService.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { DateTime } from "luxon"; -import { AccessToken, WebhookConfig } from "@/types"; -import { planetCalculations } from "@/planets"; - -export class NotificationService { - private notificationState = new Map(); - private lastWebhookTime = 0; - private readonly WEBHOOK_COOLDOWN = 5000; // 5 seconds between webhooks - - public checkAndNotify = async ( - characters: AccessToken[], - webhookConfig: WebhookConfig - ): Promise => { - if (!webhookConfig.enabled) return; - - const notifications: string[] = []; - const now = DateTime.now(); - - for (const character of characters) { - for (const planet of character.planets) { - const planetDetails = planetCalculations(planet); - - // Check extractors for expiry warnings - for (const extractor of planetDetails.extractors) { - if (extractor.expiry_time) { - const expiryTime = DateTime.fromISO(extractor.expiry_time); - const identifier = `${character.character.name}-${planet.planet_id}-${extractor.pin_id}`; - - // Check if already expired - if (expiryTime <= now) { - if (!this.notificationState.get(`${identifier}-expired`)) { - notifications.push(`🚨 EXTRACTOR EXPIRED - ${character.character.name} Planet ${planet.planet_id}`); - this.notificationState.set(`${identifier}-expired`, true); - } - } else { - // Check if expiring soon (within the warning threshold) - const warningThreshold = this.parseDuration(webhookConfig.expiryThreshold || "P12H"); - const warningTime = now.plus(warningThreshold); - - if (expiryTime <= warningTime && expiryTime > now) { - if (!this.notificationState.get(`${identifier}-warning`)) { - const hoursLeft = Math.round(expiryTime.diff(now, "hours").hours); - notifications.push(`⚠️ EXTRACTOR EXPIRING SOON - ${character.character.name} Planet ${planet.planet_id} (${hoursLeft}h left)`); - this.notificationState.set(`${identifier}-warning`, true); - } - } - } - } - } - - // Check storage capacity - for (const storage of planetDetails.storageInfo) { - const identifier = `${character.character.name}-${planet.planet_id}-${storage.type_id}`; - - if (storage.fillRate >= (webhookConfig.storageCriticalThreshold || 100)) { - if (!this.notificationState.get(`${identifier}-critical`)) { - notifications.push(`🚨 STORAGE CRITICAL - ${character.character.name} Planet ${planet.planet_id} (${storage.fillRate.toFixed(1)}%)`); - this.notificationState.set(`${identifier}-critical`, true); - } - } else if (storage.fillRate >= (webhookConfig.storageWarningThreshold || 85)) { - if (!this.notificationState.get(`${identifier}-warning`)) { - notifications.push(`⚠️ STORAGE WARNING - ${character.character.name} Planet ${planet.planet_id} (${storage.fillRate.toFixed(1)}%)`); - this.notificationState.set(`${identifier}-warning`, true); - } - } - } - } - } - - if (notifications.length > 0) { - await this.sendWebhook(notifications.join("\n")); - } - }; - - private parseDuration(duration: string): any { - // Parse ISO 8601 duration (e.g., "P12H", "P1D", "PT2H30M") - try { - return DateTime.fromISO(`2000-01-01T00:00:00Z`).plus({ [duration.slice(-1) === 'H' ? 'hours' : duration.slice(-1) === 'D' ? 'days' : 'minutes' ]: parseInt(duration.slice(1, -1)) }).diff(DateTime.fromISO(`2000-01-01T00:00:00Z`)); - } catch { - // Default to 12 hours if parsing fails - return { hours: 12 }; - } - } - - private sendWebhook = async (content: string): Promise => { - try { - const response = await fetch("/api/webhook", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ content }), - }); - - if (!response.ok) { - throw new Error(`Webhook failed: ${response.status}`); - } - } catch (error) { - console.error("Webhook error:", error); - } - }; -} - -export const notificationService = new NotificationService(); diff --git a/src/utils/webhookService.ts b/src/utils/webhookService.ts deleted file mode 100644 index 14f881b..0000000 --- a/src/utils/webhookService.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { DateTime, Duration } from 'luxon'; -import { Pin, PlanetWithInfo, AccessToken } from '@/types'; -import { StorageInfo } from '@/types/planet'; -import { WebhookConfig, WebhookPayload } from '@/types/webhook'; -import { STORAGE_IDS, LAUNCHPAD_IDS, PI_TYPES_MAP, STORAGE_CAPACITIES, PI_PRODUCT_VOLUMES } from '@/const'; -import { shouldSendWebhook, markWebhookSent } from './webhookTracker'; - -const sendWebhook = async (payload: WebhookPayload, config: WebhookConfig): Promise => { - try { - console.log('🔔 Sending webhook:', payload); - - // Include Zulip configuration in the payload - const webhookPayload = { - ...payload, - zulipUrl: config.zulipUrl, - zulipEmail: config.zulipEmail, - zulipApiKey: config.zulipApiKey, - zulipStream: config.zulipStream - }; - - const response = await fetch('/api/webhook', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(webhookPayload), - }); - - if (response.ok) { - const result = await response.json(); - console.log('✅ Webhook sent successfully:', result.message); - } else { - const error = await response.text(); - console.log('❌ Webhook failed:', response.status, response.statusText, error); - } - - return response.ok; - } catch (error) { - console.error('❌ Failed to send webhook:', error); - return false; - } -}; - -export const checkExtractorExpiry = async ( - character: AccessToken, - planet: PlanetWithInfo, - extractors: Pin[], - config: WebhookConfig -): Promise => { - if (!config.enabled) return; - - if (extractors.length === 0) { - console.log(`📊 No extractors found on ${planet.infoUniverse.name}`); - return; - } - - console.log(`⏰ Checking ${extractors.length} extractors on ${planet.infoUniverse.name}`); - const now = DateTime.now(); - const expiryThreshold = Duration.fromISO(config.expiryThreshold); - - for (const extractor of extractors) { - if (!extractor.expiry_time || !extractor.extractor_details?.product_type_id) continue; - - const expiryTime = DateTime.fromISO(extractor.expiry_time); - const timeUntilExpiry = expiryTime.diff(now); - const hoursRemaining = timeUntilExpiry.as('hours'); - - const productType = PI_TYPES_MAP[extractor.extractor_details.product_type_id]; - const extractorType = productType?.name || `Type ${extractor.extractor_details.product_type_id}`; - - // Check if extractor has expired - if (expiryTime <= now) { - console.log(`⏰ Extractor expired: ${extractorType} on ${planet.infoUniverse.name}`); - const event = 'done'; - if (shouldSendWebhook(character.character.characterId, planet.planet_id, extractor.expiry_time, event)) { - const payload: WebhookPayload = { - type: 'extractor_expired', - message: `Extractor producing ${extractorType} has expired on ${planet.infoUniverse.name}`, - characterName: character.character.name, - planetName: planet.infoUniverse.name, - details: { - extractorType, - hoursRemaining: 0 - }, - timestamp: now.toISO() - }; - - if (await sendWebhook(payload, config)) { - markWebhookSent(character.character.characterId, planet.planet_id, extractor.expiry_time, event); - } - } - } - // Check if extractor is about to expire - else if (timeUntilExpiry <= expiryThreshold) { - console.log(`⚠️ Extractor expiring soon: ${extractorType} on ${planet.infoUniverse.name} (${hoursRemaining.toFixed(1)}h remaining)`); - const event = 'nearly done'; - if (shouldSendWebhook(character.character.characterId, planet.planet_id, extractor.expiry_time, event)) { - const payload: WebhookPayload = { - type: 'extractor_expiring', - message: `Extractor producing ${extractorType} will expire in ${hoursRemaining.toFixed(1)} hours on ${planet.infoUniverse.name}`, - characterName: character.character.name, - planetName: planet.infoUniverse.name, - details: { - extractorType, - hoursRemaining: Math.max(0, hoursRemaining) - }, - timestamp: now.toISO() - }; - - if (await sendWebhook(payload, config)) { - markWebhookSent(character.character.characterId, planet.planet_id, extractor.expiry_time, event); - } - } - } - } -}; - -export const checkStorageCapacity = async ( - character: AccessToken, - planet: PlanetWithInfo, - storageInfo: StorageInfo[], - config: WebhookConfig -): Promise => { - if (!config.enabled) return; - - if (storageInfo.length === 0) { - console.log(`📦 No storage facilities found on ${planet.infoUniverse.name}`); - return; - } - - console.log(`📦 Checking ${storageInfo.length} storage facilities on ${planet.infoUniverse.name}`); - const now = DateTime.now(); - - for (const storage of storageInfo) { - const fillPercentage = (storage.used / storage.capacity) * 100; - const isLaunchpad = LAUNCHPAD_IDS.includes(storage.type_id); - const storageTypeName = PI_TYPES_MAP[storage.type_id]?.name || `Storage ${storage.type_id}`; - - // Check for critical (100%) storage - if (fillPercentage >= config.storageCriticalThreshold) { - const webhookType = isLaunchpad ? 'launchpad_full' : 'storage_full'; - console.log(`🚨 ${storageTypeName} is ${fillPercentage.toFixed(1)}% full on ${planet.infoUniverse.name}`); - - if (shouldSendWebhook(character.character.characterId, planet.planet_id, webhookType, undefined, storage.type_id)) { - const payload: WebhookPayload = { - type: webhookType, - message: `${storageTypeName} is ${fillPercentage.toFixed(1)}% full on ${planet.infoUniverse.name}`, - characterName: character.character.name, - planetName: planet.infoUniverse.name, - details: { - storageUsed: storage.used, - storageCapacity: storage.capacity, - fillPercentage: fillPercentage - }, - timestamp: now.toISO() - }; - - if (await sendWebhook(payload, config)) { - markWebhookSent(character.character.characterId, planet.planet_id, webhookType, undefined, storage.type_id); - } - } - } - // Check for warning threshold - else if (fillPercentage >= config.storageWarningThreshold) { - const webhookType = isLaunchpad ? 'launchpad_almost_full' : 'storage_almost_full'; - console.log(`⚠️ ${storageTypeName} is ${fillPercentage.toFixed(1)}% full on ${planet.infoUniverse.name}`); - - if (shouldSendWebhook(character.character.characterId, planet.planet_id, webhookType, undefined, storage.type_id)) { - const payload: WebhookPayload = { - type: webhookType, - message: `${storageTypeName} is ${fillPercentage.toFixed(1)}% full on ${planet.infoUniverse.name}`, - characterName: character.character.name, - planetName: planet.infoUniverse.name, - details: { - storageUsed: storage.used, - storageCapacity: storage.capacity, - fillPercentage: fillPercentage - }, - timestamp: now.toISO() - }; - - if (await sendWebhook(payload, config)) { - markWebhookSent(character.character.characterId, planet.planet_id, webhookType, undefined, storage.type_id); - } - } - } - } -}; - -const calculateStorageInfo = (planet: PlanetWithInfo): StorageInfo[] => { - const storageFacilities = planet.info.pins.filter((pin: Pin) => - STORAGE_IDS().some(storage => storage.type_id === pin.type_id) - ); - - return storageFacilities.map((storage: Pin): StorageInfo => { - const storageType = PI_TYPES_MAP[storage.type_id]?.name || 'Unknown'; - const storageCapacity = STORAGE_CAPACITIES[storage.type_id] || 0; - - const totalVolume = (storage.contents || []) - .reduce((sum: number, item) => { - const volume = PI_PRODUCT_VOLUMES[item.type_id] || 0; - return sum + (item.amount * volume); - }, 0); - - const fillRate = storageCapacity > 0 ? (totalVolume / storageCapacity) * 100 : 0; - - return { - type: storageType, - type_id: storage.type_id, - capacity: storageCapacity, - used: totalVolume, - fillRate: fillRate, - value: 0 // We don't need value for webhook checks - }; - }); -}; - -export const runWebhookChecks = async ( - character: AccessToken, - planet: PlanetWithInfo, - extractors: Pin[], - config: WebhookConfig -): Promise => { - if (!config.enabled) { - console.log('🔕 Webhooks disabled, skipping checks'); - return; - } - - console.log(`🔍 Running webhook checks for ${character.character.name} - ${planet.infoUniverse.name}`); - const storageInfo = calculateStorageInfo(planet); - - await Promise.all([ - checkExtractorExpiry(character, planet, extractors, config), - checkStorageCapacity(character, planet, storageInfo, config) - ]); -};