// Package infrastructure реализует порт CoverDownloader — // скачивание обложки аудиокниги по URL. package infrastructure import ( "context" "fmt" "io" "net/http" "os" "path/filepath" "time" ) // HTTPCoverDownloader реализует domain.CoverDownloader. type HTTPCoverDownloader struct { httpClient *http.Client } // NewHTTPCoverDownloader создаёт новый экземпляр. func NewHTTPCoverDownloader() *HTTPCoverDownloader { return &HTTPCoverDownloader{ httpClient: &http.Client{ Timeout: 30 * time.Second, }, } } // Download скачивает изображение по url и сохраняет как cover.jpg в destFolder. // Если cover.jpg уже существует — ничего не делает. func (d *HTTPCoverDownloader) Download(ctx context.Context, url string, destFolder string) error { if url == "" { return nil } // Проверяем, нет ли уже обложки coverPatterns := []string{"cover.jpg", "cover.jpeg", "cover.png", "folder.jpg", "folder.png"} for _, pattern := range coverPatterns { if _, err := os.Stat(filepath.Join(destFolder, pattern)); err == nil { return nil // обложка уже есть } } req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("ошибка создания запроса обложки: %w", err) } req.Header.Set("User-Agent", "GenAudioBookInfo/1.0") resp, err := d.httpClient.Do(req) if err != nil { return fmt.Errorf("ошибка скачивания обложки: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("сервер обложки вернул статус %d", resp.StatusCode) } coverPath := filepath.Join(destFolder, "cover.jpg") out, err := os.Create(coverPath) if err != nil { return fmt.Errorf("не удалось создать файл обложки: %w", err) } defer out.Close() if _, err := io.Copy(out, resp.Body); err != nil { return fmt.Errorf("ошибка записи обложки: %w", err) } return nil }