342 lines
8.6 KiB
Go
342 lines
8.6 KiB
Go
package scheduler
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
|
||
"github.com/robfig/cron/v3"
|
||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||
"github.com/daniil/homelab-api/internal/bot"
|
||
"github.com/daniil/homelab-api/internal/model"
|
||
"github.com/daniil/homelab-api/internal/repository"
|
||
)
|
||
|
||
type Scheduler struct {
|
||
cron *cron.Cron
|
||
bot *bot.Bot
|
||
userRepo *repository.UserRepository
|
||
taskRepo *repository.TaskRepository
|
||
habitRepo *repository.HabitRepository
|
||
}
|
||
|
||
func New(b *bot.Bot, userRepo *repository.UserRepository, taskRepo *repository.TaskRepository, habitRepo *repository.HabitRepository) *Scheduler {
|
||
return &Scheduler{
|
||
cron: cron.New(),
|
||
bot: b,
|
||
userRepo: userRepo,
|
||
taskRepo: taskRepo,
|
||
habitRepo: habitRepo,
|
||
}
|
||
}
|
||
|
||
func (s *Scheduler) Start() {
|
||
if s.bot == nil || s.bot.GetAPI() == nil {
|
||
log.Println("Scheduler: bot not configured, skipping")
|
||
return
|
||
}
|
||
|
||
// Run every minute
|
||
s.cron.AddFunc("* * * * *", s.checkNotifications)
|
||
|
||
s.cron.Start()
|
||
log.Println("Scheduler started")
|
||
}
|
||
|
||
func (s *Scheduler) Stop() {
|
||
s.cron.Stop()
|
||
}
|
||
|
||
func (s *Scheduler) checkNotifications() {
|
||
users, err := s.userRepo.GetUsersWithNotifications()
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting users: %v", err)
|
||
return
|
||
}
|
||
|
||
for _, user := range users {
|
||
go s.checkUserNotifications(user)
|
||
}
|
||
}
|
||
|
||
func (s *Scheduler) checkUserNotifications(user model.User) {
|
||
if !user.TelegramChatID.Valid {
|
||
return
|
||
}
|
||
|
||
chatID := user.TelegramChatID.Int64
|
||
|
||
// Get user timezone
|
||
loc, err := time.LoadLocation(user.Timezone)
|
||
if err != nil {
|
||
loc = time.FixedZone("Europe/Moscow", 3*60*60)
|
||
}
|
||
|
||
now := time.Now().In(loc)
|
||
currentTime := now.Format("15:04")
|
||
today := now.Format("2006-01-02")
|
||
weekday := int(now.Weekday())
|
||
|
||
// Get user's reminder times
|
||
morningTime := "09:00"
|
||
if user.MorningReminderTime.Valid && len(user.MorningReminderTime.String) >= 5 {
|
||
morningTime = user.MorningReminderTime.String[:5]
|
||
}
|
||
eveningTime := "21:00"
|
||
if user.EveningReminderTime.Valid && len(user.EveningReminderTime.String) >= 5 {
|
||
eveningTime = user.EveningReminderTime.String[:5]
|
||
}
|
||
|
||
// 1. Morning briefing
|
||
if currentTime == morningTime {
|
||
s.sendMorningBriefing(user.ID, chatID, loc)
|
||
}
|
||
|
||
// 2. Evening summary
|
||
if currentTime == eveningTime {
|
||
s.sendEveningSummary(user.ID, chatID, loc)
|
||
}
|
||
|
||
// 3. Task reminders
|
||
s.checkTaskReminders(user.ID, chatID, currentTime, today)
|
||
|
||
// 4. Habit reminders
|
||
s.checkHabitReminders(user.ID, chatID, currentTime, weekday)
|
||
}
|
||
|
||
func (s *Scheduler) sendMorningBriefing(userID, chatID int64, loc *time.Location) {
|
||
tasks, err := s.taskRepo.GetTodayTasks(userID)
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting tasks for user %d: %v", userID, err)
|
||
return
|
||
}
|
||
|
||
habits, err := s.habitRepo.ListByUser(userID, false)
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting habits for user %d: %v", userID, err)
|
||
return
|
||
}
|
||
|
||
// Filter habits for today
|
||
weekday := int(time.Now().In(loc).Weekday())
|
||
var todayHabits int
|
||
for _, habit := range habits {
|
||
if habit.Frequency == "daily" {
|
||
todayHabits++
|
||
} else {
|
||
for _, day := range habit.TargetDays {
|
||
if day == weekday {
|
||
todayHabits++
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(tasks) == 0 && todayHabits == 0 {
|
||
return // Nothing to report
|
||
}
|
||
|
||
text := "☀️ <b>Доброе утро!</b>\n\n"
|
||
|
||
if len(tasks) > 0 {
|
||
text += fmt.Sprintf("📋 Задач на сегодня: <b>%d</b>\n", len(tasks))
|
||
for i, task := range tasks {
|
||
if i >= 3 {
|
||
text += fmt.Sprintf(" ... и ещё %d\n", len(tasks)-3)
|
||
break
|
||
}
|
||
text += fmt.Sprintf(" • %s %s\n", task.Icon, task.Title)
|
||
}
|
||
text += "\n"
|
||
}
|
||
|
||
if todayHabits > 0 {
|
||
text += fmt.Sprintf("🎯 Привычек: <b>%d</b>\n", todayHabits)
|
||
}
|
||
|
||
text += "\nИспользуй /tasks и /habits для просмотра"
|
||
|
||
s.bot.SendMessage(chatID, text)
|
||
}
|
||
|
||
func (s *Scheduler) sendEveningSummary(userID, chatID int64, loc *time.Location) {
|
||
// Get tasks
|
||
tasks, err := s.taskRepo.GetTodayTasks(userID)
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting tasks for user %d: %v", userID, err)
|
||
return
|
||
}
|
||
|
||
// Count completed/incomplete tasks
|
||
var completedTasks, incompleteTasks int
|
||
for _, task := range tasks {
|
||
if task.CompletedAt.Valid {
|
||
completedTasks++
|
||
} else {
|
||
incompleteTasks++
|
||
}
|
||
}
|
||
|
||
// Get habits
|
||
habits, err := s.habitRepo.ListByUser(userID, false)
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting habits for user %d: %v", userID, err)
|
||
return
|
||
}
|
||
|
||
// Filter and count today's habits
|
||
weekday := int(time.Now().In(loc).Weekday())
|
||
var completedHabits, incompleteHabits int
|
||
var incompleteHabitNames []string
|
||
|
||
for _, habit := range habits {
|
||
isToday := false
|
||
if habit.Frequency == "daily" {
|
||
isToday = true
|
||
} else {
|
||
for _, day := range habit.TargetDays {
|
||
if day == weekday {
|
||
isToday = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if isToday {
|
||
completed, _ := s.habitRepo.IsHabitCompletedToday(habit.ID, userID)
|
||
if completed {
|
||
completedHabits++
|
||
} else {
|
||
incompleteHabits++
|
||
incompleteHabitNames = append(incompleteHabitNames, habit.Icon+" "+habit.Name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Don't send if nothing to report
|
||
totalTasks := completedTasks + incompleteTasks
|
||
totalHabits := completedHabits + incompleteHabits
|
||
if totalTasks == 0 && totalHabits == 0 {
|
||
return
|
||
}
|
||
|
||
text := "🌙 <b>Итоги дня</b>\n\n"
|
||
|
||
// Tasks summary
|
||
if totalTasks > 0 {
|
||
text += "📋 <b>Задачи:</b>\n"
|
||
text += fmt.Sprintf(" ✅ Выполнено: %d\n", completedTasks)
|
||
text += fmt.Sprintf(" ⬜ Осталось: %d\n", incompleteTasks)
|
||
text += "\n"
|
||
}
|
||
|
||
// Habits summary
|
||
if totalHabits > 0 {
|
||
text += "🎯 <b>Привычки:</b>\n"
|
||
text += fmt.Sprintf(" ✅ Выполнено: %d\n", completedHabits)
|
||
text += fmt.Sprintf(" ⬜ Осталось: %d\n", incompleteHabits)
|
||
|
||
// Show incomplete habits
|
||
if len(incompleteHabitNames) > 0 && len(incompleteHabitNames) <= 5 {
|
||
text += "\n Не выполнены:\n"
|
||
for _, name := range incompleteHabitNames {
|
||
text += fmt.Sprintf(" • %s\n", name)
|
||
}
|
||
}
|
||
text += "\n"
|
||
}
|
||
|
||
// Motivational message
|
||
taskPercent := 0
|
||
if totalTasks > 0 {
|
||
taskPercent = completedTasks * 100 / totalTasks
|
||
}
|
||
habitPercent := 0
|
||
if totalHabits > 0 {
|
||
habitPercent = completedHabits * 100 / totalHabits
|
||
}
|
||
|
||
avgPercent := (taskPercent + habitPercent) / 2
|
||
if totalTasks == 0 {
|
||
avgPercent = habitPercent
|
||
}
|
||
if totalHabits == 0 {
|
||
avgPercent = taskPercent
|
||
}
|
||
|
||
if avgPercent == 100 {
|
||
text += "🎉 Отличный день! Всё выполнено!"
|
||
} else if avgPercent >= 75 {
|
||
text += "👍 Хороший день! Почти всё сделано."
|
||
} else if avgPercent >= 50 {
|
||
text += "💪 Неплохо! Завтра будет лучше."
|
||
} else {
|
||
text += "🌱 Бывает. Завтра новый день!"
|
||
}
|
||
|
||
s.bot.SendMessage(chatID, text)
|
||
}
|
||
|
||
func (s *Scheduler) checkTaskReminders(userID, chatID int64, currentTime, today string) {
|
||
tasks, err := s.taskRepo.GetTasksWithReminder(currentTime, today)
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting task reminders: %v", err)
|
||
return
|
||
}
|
||
|
||
for _, task := range tasks {
|
||
if task.UserID != userID {
|
||
continue
|
||
}
|
||
|
||
text := fmt.Sprintf("⏰ <b>Напоминание о задаче:</b>\n\n%s <b>%s</b>", task.Icon, task.Title)
|
||
if task.Description != "" {
|
||
text += fmt.Sprintf("\n<i>%s</i>", task.Description)
|
||
}
|
||
|
||
keyboard := tgbotapi.NewInlineKeyboardMarkup(
|
||
tgbotapi.NewInlineKeyboardRow(
|
||
tgbotapi.NewInlineKeyboardButtonData("✅ Выполнено", fmt.Sprintf("donetask_%d", task.ID)),
|
||
tgbotapi.NewInlineKeyboardButtonData("⏰ +30 мин", fmt.Sprintf("snoozetask_%d", task.ID)),
|
||
),
|
||
)
|
||
|
||
s.bot.SendMessageWithKeyboard(chatID, text, &keyboard)
|
||
}
|
||
}
|
||
|
||
func (s *Scheduler) checkHabitReminders(userID, chatID int64, currentTime string, weekday int) {
|
||
habits, err := s.habitRepo.GetHabitsWithReminder(currentTime, weekday)
|
||
if err != nil {
|
||
log.Printf("Scheduler: error getting habit reminders: %v", err)
|
||
return
|
||
}
|
||
|
||
for _, habit := range habits {
|
||
if habit.UserID != userID {
|
||
continue
|
||
}
|
||
|
||
// Check if already completed today
|
||
completed, _ := s.habitRepo.IsHabitCompletedToday(habit.ID, userID)
|
||
if completed {
|
||
continue
|
||
}
|
||
|
||
text := fmt.Sprintf("⏰ <b>Напоминание о привычке:</b>\n\n%s <b>%s</b>", habit.Icon, habit.Name)
|
||
if habit.Description != "" {
|
||
text += fmt.Sprintf("\n<i>%s</i>", habit.Description)
|
||
}
|
||
|
||
keyboard := tgbotapi.NewInlineKeyboardMarkup(
|
||
tgbotapi.NewInlineKeyboardRow(
|
||
tgbotapi.NewInlineKeyboardButtonData("✅ Выполнено", fmt.Sprintf("checkhabit_%d", habit.ID)),
|
||
tgbotapi.NewInlineKeyboardButtonData("⏰ +30 мин", fmt.Sprintf("snoozehabit_%d", habit.ID)),
|
||
),
|
||
)
|
||
|
||
s.bot.SendMessageWithKeyboard(chatID, text, &keyboard)
|
||
}
|
||
}
|