feat: Telegram bot, notifications, profile settings, 365-day refresh tokens
This commit is contained in:
@@ -16,7 +16,19 @@ func (b *Bot) handleMessage(msg *tgbotapi.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
switch msg.Command() {
|
||||
cmd := msg.Command()
|
||||
|
||||
// Handle commands like /done_123 or /check_123
|
||||
if strings.HasPrefix(cmd, "done_") {
|
||||
b.handleDoneByID(msg, strings.TrimPrefix(cmd, "done_"))
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(cmd, "check_") {
|
||||
b.handleCheckByID(msg, strings.TrimPrefix(cmd, "check_"))
|
||||
return
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "start":
|
||||
b.handleStart(msg)
|
||||
case "tasks":
|
||||
@@ -32,6 +44,196 @@ func (b *Bot) handleMessage(msg *tgbotapi.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) handleCallback(callback *tgbotapi.CallbackQuery) {
|
||||
data := callback.Data
|
||||
chatID := callback.Message.Chat.ID
|
||||
messageID := callback.Message.MessageID
|
||||
|
||||
user, err := b.userRepo.GetByTelegramChatID(chatID)
|
||||
if err != nil {
|
||||
b.answerCallback(callback.ID, "❌ Аккаунт не найден")
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(data, "_")
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
action := parts[0]
|
||||
id, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
|
||||
switch action {
|
||||
case "donetask":
|
||||
err = b.taskRepo.Complete(id, user.ID)
|
||||
if err != nil {
|
||||
b.answerCallback(callback.ID, "❌ Ошибка")
|
||||
return
|
||||
}
|
||||
b.answerCallback(callback.ID, "✅ Задача выполнена!")
|
||||
b.refreshTasksMessage(chatID, messageID, user.ID)
|
||||
|
||||
case "deltask":
|
||||
err = b.taskRepo.Delete(id, user.ID)
|
||||
if err != nil {
|
||||
b.answerCallback(callback.ID, "❌ Ошибка")
|
||||
return
|
||||
}
|
||||
b.answerCallback(callback.ID, "🗑 Задача удалена")
|
||||
b.refreshTasksMessage(chatID, messageID, user.ID)
|
||||
|
||||
case "checkhabit":
|
||||
log := &model.HabitLog{
|
||||
HabitID: id,
|
||||
UserID: user.ID,
|
||||
Date: time.Now(),
|
||||
Count: 1,
|
||||
}
|
||||
err = b.habitRepo.CreateLog(log)
|
||||
if err != nil {
|
||||
b.answerCallback(callback.ID, "❌ Ошибка")
|
||||
return
|
||||
}
|
||||
b.answerCallback(callback.ID, "✅ Привычка отмечена!")
|
||||
b.refreshHabitsMessage(chatID, messageID, user.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) answerCallback(callbackID, text string) {
|
||||
callback := tgbotapi.NewCallback(callbackID, text)
|
||||
b.api.Request(callback)
|
||||
}
|
||||
|
||||
func (b *Bot) refreshTasksMessage(chatID int64, messageID int, userID int64) {
|
||||
tasks, err := b.taskRepo.GetTodayTasks(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
text, keyboard := b.buildTasksMessage(tasks)
|
||||
|
||||
edit := tgbotapi.NewEditMessageText(chatID, messageID, text)
|
||||
edit.ParseMode = "HTML"
|
||||
if keyboard != nil {
|
||||
edit.ReplyMarkup = keyboard
|
||||
}
|
||||
b.api.Send(edit)
|
||||
}
|
||||
|
||||
func (b *Bot) refreshHabitsMessage(chatID int64, messageID int, userID int64) {
|
||||
habits, _ := b.habitRepo.ListByUser(userID, false)
|
||||
|
||||
text, keyboard := b.buildHabitsMessage(habits, userID)
|
||||
|
||||
edit := tgbotapi.NewEditMessageText(chatID, messageID, text)
|
||||
edit.ParseMode = "HTML"
|
||||
if keyboard != nil {
|
||||
edit.ReplyMarkup = keyboard
|
||||
}
|
||||
b.api.Send(edit)
|
||||
}
|
||||
|
||||
func (b *Bot) buildTasksMessage(tasks []model.Task) (string, *tgbotapi.InlineKeyboardMarkup) {
|
||||
if len(tasks) == 0 {
|
||||
return "✨ На сегодня задач нет!", nil
|
||||
}
|
||||
|
||||
text := "📋 <b>Задачи на сегодня:</b>\n\n"
|
||||
var rows [][]tgbotapi.InlineKeyboardButton
|
||||
|
||||
for _, task := range tasks {
|
||||
priority := ""
|
||||
switch task.Priority {
|
||||
case 3:
|
||||
priority = "🔴 "
|
||||
case 2:
|
||||
priority = "🟡 "
|
||||
case 1:
|
||||
priority = "🔵 "
|
||||
}
|
||||
|
||||
status := "⬜"
|
||||
if task.CompletedAt.Valid {
|
||||
status = "✅"
|
||||
}
|
||||
|
||||
text += fmt.Sprintf("%s %s%s <b>%s</b>\n", status, priority, task.Icon, task.Title)
|
||||
if task.Description != "" {
|
||||
text += fmt.Sprintf(" <i>%s</i>\n", task.Description)
|
||||
}
|
||||
text += "\n"
|
||||
|
||||
// Add buttons only for incomplete tasks
|
||||
if !task.CompletedAt.Valid {
|
||||
row := []tgbotapi.InlineKeyboardButton{
|
||||
tgbotapi.NewInlineKeyboardButtonData("✅ Выполнить", fmt.Sprintf("donetask_%d", task.ID)),
|
||||
tgbotapi.NewInlineKeyboardButtonData("🗑 Удалить", fmt.Sprintf("deltask_%d", task.ID)),
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return text, nil
|
||||
}
|
||||
|
||||
keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
|
||||
return text, &keyboard
|
||||
}
|
||||
|
||||
func (b *Bot) buildHabitsMessage(habits []model.Habit, userID int64) (string, *tgbotapi.InlineKeyboardMarkup) {
|
||||
today := int(time.Now().Weekday())
|
||||
var todayHabits []model.Habit
|
||||
|
||||
for _, habit := range habits {
|
||||
if habit.Frequency == "daily" {
|
||||
todayHabits = append(todayHabits, habit)
|
||||
} else {
|
||||
for _, day := range habit.TargetDays {
|
||||
if day == today {
|
||||
todayHabits = append(todayHabits, habit)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(todayHabits) == 0 {
|
||||
return "✨ На сегодня привычек нет!", nil
|
||||
}
|
||||
|
||||
text := "🎯 <b>Привычки на сегодня:</b>\n\n"
|
||||
var rows [][]tgbotapi.InlineKeyboardButton
|
||||
|
||||
for _, habit := range todayHabits {
|
||||
completed, _ := b.habitRepo.IsHabitCompletedToday(habit.ID, userID)
|
||||
status := "⬜"
|
||||
if completed {
|
||||
status = "✅"
|
||||
}
|
||||
|
||||
text += fmt.Sprintf("%s %s <b>%s</b>\n", status, habit.Icon, habit.Name)
|
||||
if habit.Description != "" {
|
||||
text += fmt.Sprintf(" <i>%s</i>\n", habit.Description)
|
||||
}
|
||||
text += "\n"
|
||||
|
||||
if !completed {
|
||||
row := []tgbotapi.InlineKeyboardButton{
|
||||
tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("✅ %s", habit.Name), fmt.Sprintf("checkhabit_%d", habit.ID)),
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return text, nil
|
||||
}
|
||||
|
||||
keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
|
||||
return text, &keyboard
|
||||
}
|
||||
|
||||
func (b *Bot) handleStart(msg *tgbotapi.Message) {
|
||||
text := fmt.Sprintf(`👋 Привет! Я бот Pulse.
|
||||
|
||||
@@ -42,8 +244,6 @@ func (b *Bot) handleStart(msg *tgbotapi.Message) {
|
||||
Доступные команды:
|
||||
/tasks — задачи на сегодня
|
||||
/habits — привычки на сегодня
|
||||
/done <id> — отметить задачу выполненной
|
||||
/check <id> — отметить привычку выполненной
|
||||
/help — справка`, msg.Chat.ID)
|
||||
|
||||
b.SendMessage(msg.Chat.ID, text)
|
||||
@@ -55,8 +255,6 @@ func (b *Bot) handleHelp(msg *tgbotapi.Message) {
|
||||
/start — получить твой Chat ID
|
||||
/tasks — список задач на сегодня
|
||||
/habits — список привычек
|
||||
/done <id> — отметить задачу выполненной
|
||||
/check <id> — отметить привычку выполненной
|
||||
|
||||
💡 Чтобы получать уведомления, добавь свой Chat ID в настройках Pulse.`
|
||||
|
||||
@@ -80,30 +278,8 @@ func (b *Bot) handleTasks(msg *tgbotapi.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
b.SendMessage(msg.Chat.ID, "✨ На сегодня задач нет!")
|
||||
return
|
||||
}
|
||||
|
||||
text := "📋 <b>Задачи на сегодня:</b>\n\n"
|
||||
for _, task := range tasks {
|
||||
priority := ""
|
||||
switch task.Priority {
|
||||
case 3:
|
||||
priority = "🔴 "
|
||||
case 2:
|
||||
priority = "🟡 "
|
||||
case 1:
|
||||
priority = "🔵 "
|
||||
}
|
||||
text += fmt.Sprintf("%s%s <b>%s</b>\n", priority, task.Icon, task.Title)
|
||||
if task.Description != "" {
|
||||
text += fmt.Sprintf(" <i>%s</i>\n", task.Description)
|
||||
}
|
||||
text += fmt.Sprintf(" /done_%d\n\n", task.ID)
|
||||
}
|
||||
|
||||
b.SendMessage(msg.Chat.ID, text)
|
||||
text, keyboard := b.buildTasksMessage(tasks)
|
||||
b.SendMessageWithKeyboard(msg.Chat.ID, text, keyboard)
|
||||
}
|
||||
|
||||
func (b *Bot) handleHabits(msg *tgbotapi.Message) {
|
||||
@@ -123,74 +299,29 @@ func (b *Bot) handleHabits(msg *tgbotapi.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(habits) == 0 {
|
||||
b.SendMessage(msg.Chat.ID, "✨ У тебя пока нет привычек!")
|
||||
return
|
||||
}
|
||||
|
||||
// Filter habits for today
|
||||
today := int(time.Now().Weekday())
|
||||
var todayHabits []model.Habit
|
||||
for _, habit := range habits {
|
||||
if habit.Frequency == "daily" {
|
||||
todayHabits = append(todayHabits, habit)
|
||||
} else {
|
||||
for _, day := range habit.TargetDays {
|
||||
if day == today {
|
||||
todayHabits = append(todayHabits, habit)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(todayHabits) == 0 {
|
||||
b.SendMessage(msg.Chat.ID, "✨ На сегодня привычек нет!")
|
||||
return
|
||||
}
|
||||
|
||||
text := "🎯 <b>Привычки на сегодня:</b>\n\n"
|
||||
for _, habit := range todayHabits {
|
||||
completed, _ := b.habitRepo.IsHabitCompletedToday(habit.ID, user.ID)
|
||||
status := "⬜"
|
||||
if completed {
|
||||
status = "✅"
|
||||
}
|
||||
text += fmt.Sprintf("%s %s <b>%s</b>\n", status, habit.Icon, habit.Name)
|
||||
if habit.Description != "" {
|
||||
text += fmt.Sprintf(" <i>%s</i>\n", habit.Description)
|
||||
}
|
||||
if !completed {
|
||||
text += fmt.Sprintf(" /check_%d\n", habit.ID)
|
||||
}
|
||||
text += "\n"
|
||||
}
|
||||
|
||||
b.SendMessage(msg.Chat.ID, text)
|
||||
text, keyboard := b.buildHabitsMessage(habits, user.ID)
|
||||
b.SendMessageWithKeyboard(msg.Chat.ID, text, keyboard)
|
||||
}
|
||||
|
||||
func (b *Bot) handleDone(msg *tgbotapi.Message) {
|
||||
args := msg.CommandArguments()
|
||||
if args == "" {
|
||||
b.SendMessage(msg.Chat.ID, "❌ Укажи ID задачи: /done <id>")
|
||||
return
|
||||
}
|
||||
b.handleDoneByID(msg, args)
|
||||
}
|
||||
|
||||
func (b *Bot) handleDoneByID(msg *tgbotapi.Message, idStr string) {
|
||||
user, err := b.userRepo.GetByTelegramChatID(msg.Chat.ID)
|
||||
if err != nil {
|
||||
b.SendMessage(msg.Chat.ID, "❌ Аккаунт не найден")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse task ID from command argument or from command like /done_123
|
||||
var taskID int64
|
||||
args := msg.CommandArguments()
|
||||
if args != "" {
|
||||
taskID, _ = strconv.ParseInt(args, 10, 64)
|
||||
} else {
|
||||
// Try to parse from command like /done_123
|
||||
cmd := msg.Command()
|
||||
if strings.HasPrefix(cmd, "done_") {
|
||||
taskID, _ = strconv.ParseInt(strings.TrimPrefix(cmd, "done_"), 10, 64)
|
||||
}
|
||||
}
|
||||
|
||||
taskID, _ := strconv.ParseInt(idStr, 10, 64)
|
||||
if taskID == 0 {
|
||||
b.SendMessage(msg.Chat.ID, "❌ Укажи ID задачи: /done <id>")
|
||||
b.SendMessage(msg.Chat.ID, "❌ Неверный ID задачи")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -208,31 +339,27 @@ func (b *Bot) handleDone(msg *tgbotapi.Message) {
|
||||
}
|
||||
|
||||
func (b *Bot) handleCheck(msg *tgbotapi.Message) {
|
||||
args := msg.CommandArguments()
|
||||
if args == "" {
|
||||
b.SendMessage(msg.Chat.ID, "❌ Укажи ID привычки: /check <id>")
|
||||
return
|
||||
}
|
||||
b.handleCheckByID(msg, args)
|
||||
}
|
||||
|
||||
func (b *Bot) handleCheckByID(msg *tgbotapi.Message, idStr string) {
|
||||
user, err := b.userRepo.GetByTelegramChatID(msg.Chat.ID)
|
||||
if err != nil {
|
||||
b.SendMessage(msg.Chat.ID, "❌ Аккаунт не найден")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse habit ID from command argument or from command like /check_123
|
||||
var habitID int64
|
||||
args := msg.CommandArguments()
|
||||
if args != "" {
|
||||
habitID, _ = strconv.ParseInt(args, 10, 64)
|
||||
} else {
|
||||
// Try to parse from command like /check_123
|
||||
cmd := msg.Command()
|
||||
if strings.HasPrefix(cmd, "check_") {
|
||||
habitID, _ = strconv.ParseInt(strings.TrimPrefix(cmd, "check_"), 10, 64)
|
||||
}
|
||||
}
|
||||
|
||||
habitID, _ := strconv.ParseInt(idStr, 10, 64)
|
||||
if habitID == 0 {
|
||||
b.SendMessage(msg.Chat.ID, "❌ Укажи ID привычки: /check <id>")
|
||||
b.SendMessage(msg.Chat.ID, "❌ Неверный ID привычки")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify habit exists and belongs to user
|
||||
_, err = b.habitRepo.GetByID(habitID, user.ID)
|
||||
if err != nil {
|
||||
if err == repository.ErrHabitNotFound {
|
||||
@@ -243,7 +370,6 @@ func (b *Bot) handleCheck(msg *tgbotapi.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create log for today
|
||||
log := &model.HabitLog{
|
||||
HabitID: habitID,
|
||||
UserID: user.ID,
|
||||
|
||||
Reference in New Issue
Block a user