227 lines
8.4 KiB
TypeScript
227 lines
8.4 KiB
TypeScript
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): Promise<boolean> => {
|
|
try {
|
|
console.log('🔔 Sending webhook:', payload);
|
|
const response = await fetch('/api/webhook', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
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<void> => {
|
|
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)) {
|
|
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)) {
|
|
markWebhookSent(character.character.characterId, planet.planet_id, extractor.expiry_time, event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
export const checkStorageCapacity = async (
|
|
character: AccessToken,
|
|
planet: PlanetWithInfo,
|
|
storageInfo: StorageInfo[],
|
|
config: WebhookConfig
|
|
): Promise<void> => {
|
|
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)) {
|
|
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)) {
|
|
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<void> => {
|
|
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)
|
|
]);
|
|
};
|