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)