feat: Telegram bot, notifications, profile settings, 365-day refresh tokens

This commit is contained in:
Cosmo
2026-02-06 14:11:26 +00:00
parent 9e467b0448
commit afeb3adddf
7 changed files with 448 additions and 128 deletions

View File

@@ -6,6 +6,7 @@ import (
"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"
@@ -76,15 +77,30 @@ func (s *Scheduler) checkUserNotifications(user model.User) {
today := now.Format("2006-01-02")
weekday := int(now.Weekday())
// 1. Morning briefing at 09:00
if currentTime == "09:00" {
// 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. Task reminders
// 2. Evening summary
if currentTime == eveningTime {
s.sendEveningSummary(user.ID, chatID, loc)
}
// 3. Task reminders
s.checkTaskReminders(user.ID, chatID, currentTime, today)
// 3. Habit reminders
// 4. Habit reminders
s.checkHabitReminders(user.ID, chatID, currentTime, weekday)
}
@@ -139,7 +155,125 @@ func (s *Scheduler) sendMorningBriefing(userID, chatID int64, loc *time.Location
text += fmt.Sprintf("🎯 Привычек: <b>%d</b>\n", todayHabits)
}
text += "\n/tasks — посмотреть задачи\n/habits посмотреть привычки"
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)
}
@@ -160,9 +294,15 @@ func (s *Scheduler) checkTaskReminders(userID, chatID int64, currentTime, today
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)
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)
}
}
@@ -188,8 +328,14 @@ func (s *Scheduler) checkHabitReminders(userID, chatID int64, currentTime string
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)
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)
}
}