vkashti-bots / reservations.js
reservations.js
Raw
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)