All checks were successful
continuous-integration/drone/push Build is passing
207 lines
7.9 KiB
Go
207 lines
7.9 KiB
Go
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 <yandex_track_id> - Найти трек в кэше по ID\n" +
|
||
"/warm <URL> - \"Прогреть\" кэш для альбома или исполнителя (в разработке)\n" +
|
||
"/warm --from-dir <path> - Прогреть кэш из локальной директории внутри контейнера"
|
||
|
||
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)
|
||
}()
|
||
}
|