import dotenv from 'dotenv'
dotenv.config()
import { Client, GatewayIntentBits } from 'discord.js'
import { logger, formatCustomDate } from './utils.js'
import OpenAI from 'openai'
import moment from 'moment-timezone'
const openai = new OpenAI()
import { showDailyReservations } from './reservations_delete.js'
import {
createReservation as supabaseCreateReservation,
getReservationsForPeriod,
} from './supabase/reservationsHelper.js'
import functions from './openai/reservation_functions.js'
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMessageReactions,
],
})
const botToken = process.env.DISCORD_BOT_TOKEN
// Function to extract intent and parameters from a message using OpenAI
export async function extractIntentAndParameters(messageContent) {
const date = new Date()
const dayOfWeek = date.toLocaleDateString('bg-BG', { weekday: 'long' })
const currentDate = `${date.toISOString().split('T')[0]} (${dayOfWeek})`
const messages = [
{
role: 'system',
content: `You are a reservation assistant bot. When given a message, identify the intended action and extract the parameters required to perform the action.
Important:
- Do not infer any information that is not explicitly mentioned.
- When dates are relative like "утре", "другата седмица", etc., infer the actual date based on the current date (${currentDate}) in YYYY-MM-DD format.
- Output the parameters in English for consistency.
Respond by calling one of the functions with the extracted parameters.`,
},
{ role: 'user', content: messageContent },
]
try {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: messages,
functions: functions,
function_call: 'auto',
})
const message = response.choices[0].message
if (message.function_call) {
const action = message.function_call.name
let parameters
try {
parameters = JSON.parse(message.function_call.arguments)
} catch (parseErr) {
console.error('Error parsing function arguments:', parseErr)
throw new Error(
'Не можах да разбера вашата заявка. Моля, опитайте отново с повече детайли.'
)
}
logger(`Action: ${action}, Parameters:`)
console.dir(parameters, { depth: null })
return { action, parameters }
} else {
throw new Error(
'Не можах да разбера вашата заявка. Моля, опитайте отново с повече детайли.'
)
}
} catch (error) {
console.error('Error calling OpenAI API:', error)
throw new Error(
error.message ||
'Грешка при обработка на вашата заявка. Моля, опитайте отново по-късно.'
)
}
}
// Function to save or update the reservation to Supabase
async function createReservation(reservation, channel) {
let { person_name, phone, from_date, to_date, persons, caparo, description } = reservation
if (!person_name || person_name.trim() === '') {
person_name = 'Клиент'
reservation.person_name = person_name
}
// Convert from_date and to_date to UTC
from_date = moment.tz(from_date, 'Europe/Sofia').utc().format()
if (to_date) {
to_date = moment.tz(to_date, 'Europe/Sofia').utc().format()
}
try {
const result = await supabaseCreateReservation({
person_name,
phone,
from_date,
to_date,
persons,
caparo,
description
})
if (result.success) {
channel.send('Резервацията е създадена успешно.')
// update reservations channel message if the reservation is for today
const today = new Date().toISOString().split('T')[0]
if (from_date.split('T')[0] === today) {
showDailyReservations(channel)
}
} else {
channel.send('Хм, записването не мина по план. Нека опитаме пак! 🤔')
}
} catch (err) {
console.error('Error saving the reservation:', err)
channel.send('Хм, записването не мина по план. Нека опитаме пак! 🤔')
}
}
// Function to display reservations within a date range from Supabase
export const showReservations = async (startDate, endDate, channel) => {
try {
const result = await getReservationsForPeriod(startDate, endDate)
if (result.error) {
channel.send('Неуспешно извличане на резервации.')
return
}
const reservations = result.data
if (reservations.length === 0) {
channel.send('Няма резервации за избрания период.')
} else {
// Order reservations by from_date
reservations.sort((a, b) => new Date(a.from_date) - new Date(b.from_date))
const reservationMessages = reservations.map((reservation) => {
const formattedDate = formatCustomDate(reservation.from_date)
const time = moment.tz(reservation.from_date, 'UTC').tz('Europe/Sofia').format('HH:mm')
let reservationDetails = `📅 ${formattedDate} ${time} | **${
reservation.person_name || ''
}** | 👥 ${reservation.persons} гости | ${
reservation.description || 'Без информация'
}`
if (reservation.phone) {
reservationDetails += ` | 📞 ${reservation.phone}`
}
return reservationDetails
})
const dateRange =
startDate === endDate
? `за ${formatCustomDate(startDate)}`
: `за периода от ${formatCustomDate(startDate)} до ${formatCustomDate(
endDate
)}`
const message = `Резервации ${dateRange}:\n\n${reservationMessages.join(
'\n'
)}`
channel.send(message)
}
} catch (error) {
console.error('Error processing reservations:', error)
channel.send('Неуспешно извличане на резервации.')
}
}
export const handleReservationMessage = async (message) => {
const content = message.content.trim()
try {
const { action, parameters } = await extractIntentAndParameters(content)
switch (action) {
case 'CreateReservation':
await createReservation(parameters, message.channel)
await showReservations(
parameters.from_date.split('T')[0],
parameters.from_date.split('T')[0],
message.channel
)
break
case 'ShowReservations':
showReservations(
parameters.startDate,
parameters.endDate,
message.channel
)
break
case 'UknownAction':
message.channel.send(parameters.message)
break
default:
logger('Unknown action:', action)
message.channel.send('Не разпознавам командата.')
}
} catch (error) {
console.error('Error processing message:', error)
message.channel.send(
error.message ||
'Съжалявам, възникна грешка при обработката на вашата заявка.'
)
}
}
// Start the bot
client.login(botToken)