import dotenv from 'dotenv' dotenv.config() import { Client, GatewayIntentBits } from 'discord.js' import cron from 'node-cron' import { deleteAllMessagesInChannel, logger } from './utils.js' import { showReservations } from './reservations.js' import { createClient } from '@supabase/supabase-js' // Initialize Supabase client if (!process.env.SUPABASE_URL || !process.env.SUPABASE_ANON_KEY) { throw new Error('Missing Supabase environment variables'); } const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ) const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }) const botToken = process.env.DISCORD_BOT_TOKEN const reservationsChannelId = process.env.DISCORD_RESERVATIONS_CHANNEL_ID // Debounce function to prevent excessive updates let debounceTimer = null; const DEBOUNCE_DELAY = 3000; // 3 seconds const debouncedShowDailyReservations = async (channel) => { if (debounceTimer) { clearTimeout(debounceTimer); } debounceTimer = setTimeout(async () => { await showDailyReservations(channel); debounceTimer = null; }, DEBOUNCE_DELAY); } export const showDailyReservations = async (customChannel) => { try { const channel = customChannel || await client.channels.fetch(reservationsChannelId) await deleteAllMessagesInChannel(channel) const today = new Date() const startDate = new Date(today.setHours(0, 0, 0, 0)).toISOString() const endDate = new Date(today.setHours(23, 59, 59, 999)).toISOString() showReservations(startDate, endDate, channel) } catch (error) { console.error('Error during scheduled task:', error) } } // Function to check if a date is today function isToday(date) { if (!date || isNaN(date.getTime())) { return false; } // Convert the input date from UTC to Europe/Sofia timezone const sofiaDate = new Date(date.getTime() + (3 * 60 * 60 * 1000)); // Add 3 hours for Sofia timezone const today = new Date() return sofiaDate.getDate() === today.getDate() && sofiaDate.getMonth() === today.getMonth() && sofiaDate.getFullYear() === today.getFullYear() } // Function to set up Supabase real-time subscription function setupRealtimeSubscription() { const subscription = supabase .channel('reservations_changes') .on( 'postgres_changes', { event: '*', // Listen to all changes (INSERT, UPDATE, DELETE) schema: 'public', table: 'reservations' }, async (payload) => { try { // Log the event const eventType = payload.eventType; const id = payload.new?.id || payload.old?.id; logger(`Reservation ${eventType} detected for ID: ${id}`); // Get the reservation channel const channel = await client.channels.fetch(reservationsChannelId); // Always refresh the channel on DELETE events if (eventType === 'DELETE') { logger(`Reservation deleted, updating Discord channel...`); await debouncedShowDailyReservations(channel); return; } // Check if the change affects today's reservations const reservationDate = new Date(payload.new?.from_date || payload.old?.from_date); if (isToday(reservationDate)) { logger(`Today's reservation changed, updating Discord channel...`); await debouncedShowDailyReservations(channel); } } catch (error) { console.error('Error handling real-time update:', error); } } ) .subscribe((status) => { logger(`Supabase subscription status: ${status}`); if (status !== 'SUBSCRIBED') { // Attempt to reconnect if subscription fails logger('Attempting to reconnect to Supabase in 5 seconds...'); setTimeout(() => { subscription.unsubscribe(); setupRealtimeSubscription(); }, 5000); } }); return subscription; } client.once('ready', async () => { logger('Reservations bot is ready!') // Set up real-time subscription setupRealtimeSubscription(); // Set up daily cron job cron.schedule( '30 9 * * *', async () => { await showDailyReservations(); }, { timezone: 'Europe/Sofia' } ) }) // Start the bot client.login(botToken)