diff --git a/src/app/page.tsx b/src/app/page.tsx index a1215fb..9349b8c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,21 +8,101 @@ import { AccessToken, CharacterUpdate, Env, PlanetWithInfo } from "../types"; import { MainGrid } from "./components/MainGrid"; import { refreshToken } from "@/esi-sso"; import { - CharacterContext, - ColorContext, - ColorSelectionType, - SessionContext, - defaultColors, + CharacterContext, + ColorContext, + ColorSelectionType, + SessionContext, + defaultColors, } from "./context/Context"; import { useSearchParams } from "next/navigation"; import { EvePraisalResult, fetchAllPrices } from "@/eve-praisal"; import { getPlanet, getPlanetUniverse, getPlanets } from "@/planets"; import { PlanetConfig } from "@/types"; -import { runWebhookChecks } from "@/utils/webhookService"; +// Webhook service removed - using direct API calls import { WebhookConfig } from "@/types/webhook"; import { cleanupOldWebhookStates } from "@/utils/webhookTracker"; import { planetCalculations } from "@/planets"; +// Webhook check functions +const checkExtractorExpiry = async (character: any, planet: any, extractors: any[], config: WebhookConfig) => { + if (!config.enabled || !config.zulipUrl) return; + + const now = new Date(); + const expiryThreshold = new Date(now.getTime() + 12 * 60 * 60 * 1000); // 12 hours from now + + for (const extractor of extractors) { + if (extractor.expiry_time) { + const expiryTime = new Date(extractor.expiry_time); + if (expiryTime <= expiryThreshold) { + const hoursRemaining = Math.round((expiryTime.getTime() - now.getTime()) / (1000 * 60 * 60)); + const message = `⚠️ Extractor ${extractor.type_name} expires in ${hoursRemaining} hours`; + + await fetch('/api/zulip-webhook', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + type: 'extractor_expiring', + message, + characterName: character.character.name, + planetName: planet.infoUniverse.name, + timestamp: now.toISOString(), + zulipUrl: config.zulipUrl, + zulipEmail: config.zulipEmail, + zulipApiKey: config.zulipApiKey, + zulipStream: config.zulipStream + }) + }); + } + } + } +}; + +const checkStorageCapacity = async (character: any, planet: any, storage: any[], config: WebhookConfig) => { + if (!config.enabled || !config.zulipUrl) return; + + for (const storageItem of storage) { + const fillPercentage = (storageItem.used / storageItem.capacity) * 100; + + if (fillPercentage >= config.storageCriticalThreshold) { + const message = `🚨 Storage ${storageItem.type_name} is ${fillPercentage.toFixed(1)}% full!`; + + await fetch('/api/zulip-webhook', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + type: 'storage_full', + message, + characterName: character.character.name, + planetName: planet.infoUniverse.name, + timestamp: new Date().toISOString(), + zulipUrl: config.zulipUrl, + zulipEmail: config.zulipEmail, + zulipApiKey: config.zulipApiKey, + zulipStream: config.zulipStream + }) + }); + } else if (fillPercentage >= config.storageWarningThreshold) { + const message = `⚠️ Storage ${storageItem.type_name} is ${fillPercentage.toFixed(1)}% full`; + + await fetch('/api/zulip-webhook', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + type: 'storage_almost_full', + message, + characterName: character.character.name, + planetName: planet.infoUniverse.name, + timestamp: new Date().toISOString(), + zulipUrl: config.zulipUrl, + zulipEmail: config.zulipEmail, + zulipApiKey: config.zulipApiKey, + zulipStream: config.zulipStream + }) + }); + } + } +}; + // Add batch processing utility const processInBatches = async ( items: T[], @@ -152,29 +232,23 @@ const Home = () => { info: await getPlanet(c, p), infoUniverse: await getPlanetUniverse(p), }) - ); - - // Run webhook checks for each planet if webhooks are enabled - if (webhookConfig.enabled) { - console.log(`🔍 Running webhook checks for ${c.character.name} (${planetsWithInfo.length} planets)`); - for (const planet of planetsWithInfo) { - try { - const calculations = planetCalculations(planet); - await runWebhookChecks( - c, - planet, - calculations.extractors, - webhookConfig - ); - } catch (error) { - console.warn('Webhook check failed for planet:', planet.infoUniverse.name, error); + ); + + // Run webhook checks for each planet if webhooks are enabled + if (webhookConfig.enabled) { + console.log(`🔍 Running webhook checks for ${c.character.name} (${planetsWithInfo.length} planets)`); + for (const planet of planetsWithInfo) { + try { + const calculations = planetCalculations(planet); + await checkExtractorExpiry(c, planet, calculations.extractors, webhookConfig); + await checkStorageCapacity(c, planet, planet.info.pins, webhookConfig); + } catch (error) { + console.warn('Webhook check failed for planet:', planet.infoUniverse.name, error); + } } } - } else { - console.log('🔕 Webhooks disabled, skipping checks'); - } - - return { + + return { ...c, planets: planetsWithInfo, }; @@ -405,19 +479,7 @@ const Home = () => { for (const character of currentCharacters) { if (character.needsLogin || !character.planets) continue; - for (const planet of character.planets) { - try { - const calculations = planetCalculations(planet); - await runWebhookChecks( - character, - planet, - calculations.extractors, - webhookConfig - ); - } catch (error) { - console.warn('Regular webhook check failed for planet:', planet.infoUniverse.name, error); - } - } + // Webhook checks removed } // Cleanup old webhook states diff --git a/src/pages/api/zulip-webhook.ts b/src/pages/api/zulip-webhook.ts index d287842..428a4b6 100644 --- a/src/pages/api/zulip-webhook.ts +++ b/src/pages/api/zulip-webhook.ts @@ -81,7 +81,7 @@ export default async function handler( } // Create topic in format: CharacterName-PlanetName-ActionType - const actionType = payload.type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + const actionType = payload.type.replace(/_/g, ' ').replace(/\b\w/g, (l: string) => l.toUpperCase()); const topic = `${payload.characterName}-${payload.planetName}-${actionType}`; // Format content with all the details