feat: add Telegram bot with notifications and scheduler
This commit is contained in:
195
internal/scheduler/scheduler.go
Normal file
195
internal/scheduler/scheduler.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"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())
|
||||
|
||||
// 1. Morning briefing at 09:00
|
||||
if currentTime == "09:00" {
|
||||
s.sendMorningBriefing(user.ID, chatID, loc)
|
||||
}
|
||||
|
||||
// 2. Task reminders
|
||||
s.checkTaskReminders(user.ID, chatID, currentTime, today)
|
||||
|
||||
// 3. 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 — посмотреть задачи\n/habits — посмотреть привычки"
|
||||
|
||||
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)
|
||||
}
|
||||
text += fmt.Sprintf("\n\n/done_%d — отметить выполненной", task.ID)
|
||||
|
||||
s.bot.SendMessage(chatID, text)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
text += fmt.Sprintf("\n\n/check_%d — отметить выполненной", habit.ID)
|
||||
|
||||
s.bot.SendMessage(chatID, text)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user