Stop spamming notifications

This commit is contained in:
2025-09-21 16:13:59 +02:00
parent b56a950f04
commit b5dc367713
3 changed files with 104 additions and 87 deletions

View File

@@ -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));

View File

@@ -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<boolean> => {
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);
}
}
};

View File

@@ -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<string, WebhookState>();
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);
}
};
};