diff --git a/src/pages/api/webhook.ts b/src/pages/api/webhook.ts index fea5c52..d9a3055 100644 --- a/src/pages/api/webhook.ts +++ b/src/pages/api/webhook.ts @@ -32,9 +32,9 @@ export default async function handler( // Send webhook to external URL console.log('๐Ÿ”” Sending webhook to external URL:', webhookUrl); - // Use simple format as requested + // Use simple format with all the details const webhookPayload = { - content: payload.message + content: `${payload.message}\n**Character:** ${payload.characterName}\n**Planet:** ${payload.planetName}\n**Time:** ${new Date(payload.timestamp).toLocaleString()}` }; console.log('๐Ÿ“ค Sending payload:', JSON.stringify(webhookPayload, null, 2)); diff --git a/src/utils/webhookService.ts b/src/utils/webhookService.ts index 8baff91..e7bd2e2 100644 --- a/src/utils/webhookService.ts +++ b/src/utils/webhookService.ts @@ -3,7 +3,7 @@ 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, resetExtractorWebhookStates } from './webhookTracker'; +import { shouldSendWebhook, markWebhookSent } from './webhookTracker'; const sendWebhook = async (payload: WebhookPayload): Promise => { try { @@ -61,7 +61,8 @@ export const checkExtractorExpiry = async ( // Check if extractor has expired if (expiryTime <= now) { console.log(`โฐ Extractor expired: ${extractorType} on ${planet.infoUniverse.name}`); - if (shouldSendWebhook(character.character.characterId, planet.planet_id, 'extractor_expired', extractor.pin_id)) { + 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}`, @@ -75,14 +76,15 @@ export const checkExtractorExpiry = async ( }; if (await sendWebhook(payload)) { - markWebhookSent(character.character.characterId, planet.planet_id, 'extractor_expired', extractor.pin_id); + 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)`); - if (shouldSendWebhook(character.character.characterId, planet.planet_id, 'extractor_expiring', extractor.pin_id)) { + 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}`, @@ -96,14 +98,10 @@ export const checkExtractorExpiry = async ( }; if (await sendWebhook(payload)) { - markWebhookSent(character.character.characterId, planet.planet_id, 'extractor_expiring', extractor.pin_id); + markWebhookSent(character.character.characterId, planet.planet_id, extractor.expiry_time, event); } } } - // If extractor has been reset (duration is "full" again), reset webhook states - else if (timeUntilExpiry > expiryThreshold.plus({ hours: 1 })) { - resetExtractorWebhookStates(character.character.characterId, planet.planet_id, extractor.pin_id); - } } }; diff --git a/src/utils/webhookTracker.ts b/src/utils/webhookTracker.ts index f678ed1..a7315a0 100644 --- a/src/utils/webhookTracker.ts +++ b/src/utils/webhookTracker.ts @@ -1,103 +1,122 @@ -import { DateTime } from 'luxon'; -export interface WebhookState { - lastSent: string; - type: string; - planetId: number; - characterId: number; - extractorPinId?: number; - storageTypeId?: number; +interface WebhookState { + sent: boolean; + lastSent: number; } -// In-memory storage for webhook states (in production, you might want to use localStorage or a database) -const webhookStates = new Map(); - -export const generateWebhookKey = ( +// Create hash for unique events: character-planet-extractorEndTimestamp-event +export const createWebhookHash = ( characterId: number, planetId: number, - type: string, - extractorPinId?: number, - storageTypeId?: number + extractorEndTimestamp: string, + event: string ): string => { - const parts = [characterId, planetId, type]; - if (extractorPinId !== undefined) parts.push(`extractor-${extractorPinId}`); - if (storageTypeId !== undefined) parts.push(`storage-${storageTypeId}`); - return parts.join('-'); + return `${characterId}-${planetId}-${extractorEndTimestamp}-${event}`; +}; + +// Get webhook state from localStorage +const getWebhookState = (hash: string): WebhookState => { + if (typeof window === 'undefined') return { sent: false, lastSent: 0 }; + + try { + const stored = localStorage.getItem(`webhook_${hash}`); + if (stored) { + return JSON.parse(stored); + } + } catch (error) { + console.warn('Failed to parse webhook state from localStorage:', error); + } + + return { sent: false, lastSent: 0 }; +}; + +// Set webhook state in localStorage +const setWebhookState = (hash: string, state: WebhookState): void => { + if (typeof window === 'undefined') return; + + try { + localStorage.setItem(`webhook_${hash}`, JSON.stringify(state)); + } catch (error) { + console.warn('Failed to save webhook state to localStorage:', error); + } }; export const shouldSendWebhook = ( characterId: number, planetId: number, - type: string, - extractorPinId?: number, - storageTypeId?: number + extractorEndTimestamp: string, + event: string ): boolean => { - const key = generateWebhookKey(characterId, planetId, type, extractorPinId, storageTypeId); - const state = webhookStates.get(key); + const hash = createWebhookHash(characterId, planetId, extractorEndTimestamp, event); + const state = getWebhookState(hash); - if (!state) { - return true; // First time, send webhook + return !state.sent; +}; + +// Storage webhook tracking with 1-hour cooldown +export const shouldSendStorageWebhook = ( + characterId: number, + planetId: number, + storageTypeId: number, + webhookType: string +): boolean => { + const hash = `storage-${characterId}-${planetId}-${storageTypeId}-${webhookType}`; + const state = getWebhookState(hash); + + if (!state.sent) { + return true; } - const lastSent = DateTime.fromISO(state.lastSent); - const now = DateTime.now(); - - // For expired extractors, resend every hour - if (type === 'extractor_expired') { - return now.diff(lastSent, 'hours').hours >= 1; - } - - // For other alerts, resend every 4 hours - return now.diff(lastSent, 'hours').hours >= 4; + // Check if 1 hour has passed since last notification + const oneHourAgo = Date.now() - (60 * 60 * 1000); + return state.lastSent < oneHourAgo; +}; + +export const markStorageWebhookSent = ( + characterId: number, + planetId: number, + storageTypeId: number, + webhookType: string +): void => { + const hash = `storage-${characterId}-${planetId}-${storageTypeId}-${webhookType}`; + setWebhookState(hash, { sent: true, lastSent: Date.now() }); }; export const markWebhookSent = ( characterId: number, planetId: number, - type: string, - extractorPinId?: number, - storageTypeId?: number + extractorEndTimestamp: string, + event: string ): void => { - const key = generateWebhookKey(characterId, planetId, type, extractorPinId, storageTypeId); - webhookStates.set(key, { - lastSent: DateTime.now().toISO(), - type, - planetId, - characterId, - extractorPinId, - storageTypeId - }); + const hash = createWebhookHash(characterId, planetId, extractorEndTimestamp, event); + setWebhookState(hash, { sent: true, lastSent: Date.now() }); }; -export const resetWebhookState = ( - characterId: number, - planetId: number, - type: string, - extractorPinId?: number, - storageTypeId?: number -): void => { - const key = generateWebhookKey(characterId, planetId, type, extractorPinId, storageTypeId); - webhookStates.delete(key); -}; - -// Reset extractor webhook states when extractors are refreshed (duration is "full" again) -export const resetExtractorWebhookStates = ( - characterId: number, - planetId: number, - extractorPinId: number -): void => { - resetWebhookState(characterId, planetId, 'extractor_expiring', extractorPinId); - resetWebhookState(characterId, planetId, 'extractor_expired', extractorPinId); -}; - -// Clear old webhook states (older than 7 days) +// Cleanup old webhook states (older than 7 days) export const cleanupOldWebhookStates = (): void => { - const cutoff = DateTime.now().minus({ days: 7 }); + if (typeof window === 'undefined') return; - for (const [key, state] of webhookStates.entries()) { - const lastSent = DateTime.fromISO(state.lastSent); - if (lastSent < cutoff) { - webhookStates.delete(key); + const cutoff = Date.now() - (7 * 24 * 60 * 60 * 1000); // 7 days ago + + try { + const keysToRemove: string[] = []; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith('webhook_')) { + const state = JSON.parse(localStorage.getItem(key) || '{}'); + if (state.lastSent && state.lastSent < cutoff) { + keysToRemove.push(key); + } + } } + + keysToRemove.forEach(key => localStorage.removeItem(key)); + + if (keysToRemove.length > 0) { + console.log(`๐Ÿงน Cleaned up ${keysToRemove.length} old webhook states`); + } + } catch (error) { + console.warn('Failed to cleanup old webhook states:', error); } -}; +}; \ No newline at end of file