package downloader import ( "context" "errors" "fmt" "io" "net/http" "os" "path" "path/filepath" "time" ) const ( DefaultTimeout = 30 * time.Second UserAgent = "M3U8-Downloader/1.0" ) type HTTPClient struct { client *http.Client headers map[string]string } func NewHTTPClient() *HTTPClient { return &HTTPClient{ client: &http.Client{ Timeout: DefaultTimeout, }, headers: map[string]string{ "User-Agent": UserAgent, }, } } func (c *HTTPClient) SetTimeout(timeout time.Duration) { c.client.Timeout = timeout } func (c *HTTPClient) SetHeader(key, value string) { c.headers[key] = value } func (c *HTTPClient) Get(url string) ([]byte, error) { return c.GetWithContext(context.Background(), url) } func (c *HTTPClient) GetWithContext(ctx context.Context, url string) ([]byte, error) { if !ValidateURL(url) { return nil, errors.New("invalid URL format") } req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } // Set headers for key, value := range c.headers { req.Header.Set(key, value) } resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status) } data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } return data, nil } // Global client instance for convenience functions var defaultClient = NewHTTPClient() func FetchPlaylistContent(url string) (string, error) { data, err := defaultClient.Get(url) if err != nil { return "", err } return string(data), nil } func DownloadTSFile(url string, outputDir string) error { if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } fileName := path.Base(url) filePath := filepath.Join(outputDir, fileName) data, err := defaultClient.Get(url) if err != nil { return err } out, err := os.Create(filePath) if err != nil { return err } defer out.Close() _, err = out.Write(data) if err != nil { return err } return nil } func FetchPlaylistContentWithContext(ctx context.Context, url string) (string, error) { data, err := defaultClient.GetWithContext(ctx, url) if err != nil { return "", err } return string(data), nil } func SetDefaultTimeout(timeout time.Duration) { defaultClient.SetTimeout(timeout) } func SetDefaultUserAgent(userAgent string) { defaultClient.SetHeader("User-Agent", userAgent) }