package bot import ( "database/sql" "errors" "fmt" "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "worktimeBot/internal/db" "worktimeBot/pkg/i18n" ) type Handler struct { Bot *tgbotapi.BotAPI DB *db.Store Translator *i18n.Translator } func NewHandler(bot *tgbotapi.BotAPI, store *db.Store, tr *i18n.Translator) *Handler { return &Handler{Bot: bot, DB: store, Translator: tr} } // HandleMessage routes incoming messages. func (h *Handler) HandleMessage(update tgbotapi.Update) { msg := update.Message switch msg.Text { case "/clockin": if err := h.handleClockIn(); err != nil { h.replyWithError(msg.Chat.ID, err.Error()) } else { h.replyWithText(msg.Chat.ID, "clock_in") } case "/clockout": if err := h.handleClockOut(); err != nil { h.replyWithError(msg.Chat.ID, err.Error()) } else { h.replyWithText(msg.Chat.ID, "clock_out") } case "/remote": h.toggleRemote(msg) case "/report": h.handleReport(msg) case "/export": h.handleExport(msg) case "/dayoff": h.handleDayOff(msg) default: h.replyWithText(msg.Chat.ID, "unknown_command") } } // handleClockIn validates and records a clock‑in event. func (h *Handler) handleClockIn() error { last, err := h.DB.GetLastEvent() if err != nil && err != sql.ErrNoRows { return err } if last != nil && last.EventType == "in" { return errors.New(h.Translator.Get("already_clocked_in", "en")) } // Set default remote flag after first clock‑in. if _, err := h.DB.GetSetting("remote_flag"); err != nil { _ = h.DB.SetSetting("remote_flag", "onsite") } return h.DB.CreateEvent("in", time.Now().Unix(), "") } // handleClockOut validates and records a clock‑out event. func (h *Handler) handleClockOut() error { last, err := h.DB.GetLastEvent() if err != nil { return err } if last == nil { return errors.New(h.Translator.Get("error", "en")) } switch last.EventType { case "in": return h.DB.CreateEvent("out", time.Now().Unix(), "") case "out": return errors.New(h.Translator.Get("already_clocked_out", "en")) default: return errors.New(h.Translator.Get("error", "en")) } } // toggleRemote switches the remote/onsite flag. func (h *Handler) toggleRemote(msg *tgbotapi.Message) { current, err := h.DB.GetSetting("remote_flag") if err != nil { current = "onsite" } newMode := "remote" if current == "remote" { newMode = "onsite" } if err := h.DB.SetSetting("remote_flag", newMode); err != nil { h.replyWithError(msg.Chat.ID, "error") return } key := "remote_switched_on" if newMode == "onsite" { key = "remote_switched_off" } h.replyWithText(msg.Chat.ID, key) } // handleReport sends daily work time summary. func (h *Handler) handleReport(msg *tgbotapi.Message) { today := time.Now().Format("2006-01-02") events, err := h.DB.EventsForDay(today) if err != nil { h.replyWithError(msg.Chat.ID, "error") return } totals := ComputeDailyTotals(events) if totals.PairCount == 0 { h.replyWithText(msg.Chat.ID, "total_work_time") return } workStr := formatDuration(totals.TotalSeconds) breakStr := formatDuration(totals.BreakSeconds) text := fmt.Sprintf("Work: %s | Break: %s", workStr, breakStr) reply := tgbotapi.NewMessage(msg.Chat.ID, text) _, _ = h.Bot.Send(reply) } // formatDuration converts seconds to a human‑readable string. func formatDuration(seconds int64) string { hours := seconds / 3600 mins := (seconds % 3600) / 60 if hours > 0 { return fmt.Sprintf("%dh %dm", hours, mins) } return fmt.Sprintf("%dm", mins) } // handleExport generates and sends a monthly Excel report. func (h *Handler) handleExport(msg *tgbotapi.Message) { now := time.Now() year, month := now.Year(), now.Month() data, err := GenerateMonthlyReport(h.DB, year, month) if err != nil { h.replyWithError(msg.Chat.ID, "error") return } doc := tgbotapi.NewDocument(msg.Chat.ID, tgbotapi.FileBytes{ Name: fmt.Sprintf("worktime_%s.xlsx", now.Format("2006_01")), Bytes: data, }) if _, err := h.Bot.Send(doc); err != nil { h.replyWithError(msg.Chat.ID, "error") } } // handleDayOff toggles today as a day off. func (h *Handler) handleDayOff(msg *tgbotapi.Message) { today := time.Now().Format("2006-01-02") isOff, err := h.DB.IsDayOff(today) if err != nil { h.replyWithError(msg.Chat.ID, "error") return } if isOff { if err := h.DB.RemoveDayOff(today); err != nil { h.replyWithError(msg.Chat.ID, "error") return } h.replyWithText(msg.Chat.ID, "dayoff_removed") } else { if err := h.DB.SetDayOff(today, ""); err != nil { h.replyWithError(msg.Chat.ID, "error") return } h.replyWithText(msg.Chat.ID, "dayoff_set") } } func (h *Handler) replyWithText(chatID int64, key string) { text := h.Translator.Get(key, "en") reply := tgbotapi.NewMessage(chatID, text) _, _ = h.Bot.Send(reply) } func (h *Handler) replyWithError(chatID int64, key string) { text := h.Translator.Get(key, "en") reply := tgbotapi.NewMessage(chatID, text) _, _ = h.Bot.Send(reply) }