Stop spamming notifications
This commit is contained in:
@@ -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));
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user