package admin import ( "context" "fmt" "log/slog" "os" "path/filepath" "strings" "time" "gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) // Handler обрабатывает команды администратора. type Handler struct { storage interfaces.TrackStorage telegram interfaces.TelegramClient yandex interfaces.YandexMusicClient startTime time.Time } // NewHandler создает новый обработчик команд администратора. func NewHandler(storage interfaces.TrackStorage, telegram interfaces.TelegramClient, yandex interfaces.YandexMusicClient, startTime time.Time) *Handler { return &Handler{ storage: storage, telegram: telegram, yandex: yandex, startTime: startTime, } } // HandleCommand обрабатывает входящую команду. func (h *Handler) HandleCommand(ctx context.Context, message *tgbotapi.Message) { command := message.Command() args := message.CommandArguments() slog.Info("Handling admin command", "user_id", message.From.ID, "command", command, "args", args) switch command { case "help": h.handleHelp(ctx, message.Chat.ID) case "stats": h.handleStats(ctx, message.Chat.ID) case "find": h.handleFind(ctx, message.Chat.ID, args) case "warm": h.handleWarm(ctx, message.Chat.ID, args) default: if err := h.telegram.SendMessage(ctx, message.Chat.ID, "Неизвестная команда. Используйте /help для списка команд."); err != nil { slog.Error("Failed to send 'unknown command' message", "error", err, "chat_id", message.Chat.ID) } } } func (h *Handler) handleHelp(ctx context.Context, chatID int64) { helpText := "Команды администратора:\n" + "/help - Показать это сообщение\n" + "/stats - Показать статистику бота\n" + "/find - Найти трек в кэше по ID\n" + "/warm - \"Прогреть\" кэш для альбома или исполнителя (в разработке)\n" + "/warm --from-dir - Прогреть кэш из локальной директории внутри контейнера" if err := h.telegram.SendMessage(ctx, chatID, helpText); err != nil { slog.Error("Failed to send help message", "error", err, "chat_id", chatID) } } func (h *Handler) handleStats(ctx context.Context, chatID int64) { cachedTracks, err := h.storage.Count(ctx) if err != nil { slog.Error("Failed to get stats from storage", "error", err) if err := h.telegram.SendMessage(ctx, chatID, "Не удалось получить статистику из хранилища."); err != nil { slog.Error("Failed to send stats error message", "error", err, "chat_id", chatID) } return } uptime := time.Since(h.startTime).Round(time.Second) statsText := fmt.Sprintf( "📊 Статистика бота\n\n"+ "Время работы: %s\n"+ "Треков в кэше: %d", uptime, cachedTracks, ) if err := h.telegram.SendMessage(ctx, chatID, statsText); err != nil { slog.Error("Failed to send stats message", "error", err, "chat_id", chatID) } } func (h *Handler) handleFind(ctx context.Context, chatID int64, trackID string) { if trackID == "" { if err := h.telegram.SendMessage(ctx, chatID, "Пожалуйста, укажите Yandex Track ID. Пример: /find 123456"); err != nil { slog.Error("Failed to send 'missing args' message for /find", "error", err, "chat_id", chatID) } return } fileID, err := h.storage.Get(ctx, trackID) if err != nil { msg := fmt.Sprintf("Трек с ID `%s` не найден в кэше.", trackID) if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil { slog.Error("Failed to send 'track not found' message for /find", "error", err, "chat_id", chatID) } return } msg := fmt.Sprintf("Трек найден! Telegram File ID: `%s`", fileID) if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil { slog.Error("Failed to send 'track found' message for /find", "error", err, "chat_id", chatID) } } func (h *Handler) handleWarm(ctx context.Context, chatID int64, args string) { const fromDirPrefix = "--from-dir " if strings.HasPrefix(args, fromDirPrefix) { dirPath := strings.TrimPrefix(args, fromDirPrefix) h.handleWarmFromDir(ctx, chatID, dirPath) return } // Здесь будет логика для прогрева по URL if err := h.telegram.SendMessage(ctx, chatID, "Прогрев по URL находится в разработке."); err != nil { slog.Error("Failed to send 'warm in development' message", "error", err, "chat_id", chatID) } } // handleWarmFromDir запускает фоновую задачу прогрева кэша из локальной директории. func (h *Handler) handleWarmFromDir(ctx context.Context, chatID int64, dirPath string) { msg := fmt.Sprintf("Принято в обработку. Начинаю прогрев кэша из директории: `%s`", dirPath) if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil { slog.Error("Failed to send 'warm from dir started' message", "error", err, "chat_id", chatID) return } go func() { slog.Info("Starting cache warming from directory", "path", dirPath) files, err := os.ReadDir(dirPath) if err != nil { slog.Error("Failed to read directory for warming", "path", dirPath, "error", err) errMsg := fmt.Sprintf("Ошибка: не удалось прочитать директорию `%s`. Убедитесь, что она существует и доступна.", dirPath) _ = h.telegram.SendMessage(context.Background(), chatID, errMsg) return } var addedCount, skippedCount, errorCount int totalFiles := len(files) for i, file := range files { if file.IsDir() || !strings.HasSuffix(file.Name(), ".mp3") { continue } trackID := strings.TrimSuffix(file.Name(), ".mp3") fullPath := filepath.Join(dirPath, file.Name()) // 1. Проверяем, есть ли трек в кэше _, err := h.storage.Get(ctx, trackID) if err == nil { slog.Debug("Skipping already cached track", "track_id", trackID) skippedCount++ continue } // 2. Загружаем в Telegram // Поскольку метатеги уже вшиты, для отображения в кэш-канале можно использовать простые title/performer slog.Debug("Uploading track to cache channel", "track_id", trackID, "path", fullPath) fileID, err := h.telegram.SendAudioToCacheChannel(ctx, fullPath, trackID, "Pre-cached") if err != nil { slog.Error("Failed to upload pre-cached file", "track_id", trackID, "error", err) errorCount++ continue } // 3. Сохраняем в БД err = h.storage.Set(ctx, trackID, fileID) if err != nil { slog.Error("Failed to save pre-cached file to storage", "track_id", trackID, "error", err) errorCount++ continue } addedCount++ slog.Info("Successfully cached track from local file", "track_id", trackID, "file_id", fileID) // Опционально: отправляем прогресс каждые N файлов if (i+1)%1000 == 0 { progressMsg := fmt.Sprintf("Прогресс: обработано %d из %d файлов...", i+1, totalFiles) _ = h.telegram.SendMessage(context.Background(), chatID, progressMsg) } } finalMsg := fmt.Sprintf( "✅ Прогрев кэша из директории `%s` завершен.\n\n"+ "Новых треков добавлено: %d\n"+ "Треков пропущено (уже в кэше): %d\n"+ "Ошибок при обработке: %d", dirPath, addedCount, skippedCount, errorCount, ) _ = h.telegram.SendMessage(context.Background(), chatID, finalMsg) slog.Info("Finished cache warming from directory", "path", dirPath, "added", addedCount, "skipped", skippedCount, "errors", errorCount) }() }