143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
package utils
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/sipeed/picoclaw/pkg/logger"
|
|
)
|
|
|
|
// IsAudioFile checks if a file is an audio file based on its filename extension and content type.
|
|
func IsAudioFile(filename, contentType string) bool {
|
|
audioExtensions := []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"}
|
|
audioTypes := []string{"audio/", "application/ogg", "application/x-ogg"}
|
|
|
|
for _, ext := range audioExtensions {
|
|
if strings.HasSuffix(strings.ToLower(filename), ext) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
for _, audioType := range audioTypes {
|
|
if strings.HasPrefix(strings.ToLower(contentType), audioType) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// SanitizeFilename removes potentially dangerous characters from a filename
|
|
// and returns a safe version for local filesystem storage.
|
|
func SanitizeFilename(filename string) string {
|
|
// Get the base filename without path
|
|
base := filepath.Base(filename)
|
|
|
|
// Remove any directory traversal attempts
|
|
base = strings.ReplaceAll(base, "..", "")
|
|
base = strings.ReplaceAll(base, "/", "_")
|
|
base = strings.ReplaceAll(base, "\\", "_")
|
|
|
|
return base
|
|
}
|
|
|
|
// DownloadOptions holds optional parameters for downloading files
|
|
type DownloadOptions struct {
|
|
Timeout time.Duration
|
|
ExtraHeaders map[string]string
|
|
LoggerPrefix string
|
|
}
|
|
|
|
// DownloadFile downloads a file from URL to a local temp directory.
|
|
// Returns the local file path or empty string on error.
|
|
func DownloadFile(url, filename string, opts DownloadOptions) string {
|
|
// Set defaults
|
|
if opts.Timeout == 0 {
|
|
opts.Timeout = 60 * time.Second
|
|
}
|
|
if opts.LoggerPrefix == "" {
|
|
opts.LoggerPrefix = "utils"
|
|
}
|
|
|
|
mediaDir := filepath.Join(os.TempDir(), "picoclaw_media")
|
|
if err := os.MkdirAll(mediaDir, 0700); err != nil {
|
|
logger.ErrorCF(opts.LoggerPrefix, "Failed to create media directory", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return ""
|
|
}
|
|
|
|
// Generate unique filename with UUID prefix to prevent conflicts
|
|
safeName := SanitizeFilename(filename)
|
|
localPath := filepath.Join(mediaDir, uuid.New().String()[:8]+"_"+safeName)
|
|
|
|
// Create HTTP request
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
logger.ErrorCF(opts.LoggerPrefix, "Failed to create download request", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return ""
|
|
}
|
|
|
|
// Add extra headers (e.g., Authorization for Slack)
|
|
for key, value := range opts.ExtraHeaders {
|
|
req.Header.Set(key, value)
|
|
}
|
|
|
|
client := &http.Client{Timeout: opts.Timeout}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
logger.ErrorCF(opts.LoggerPrefix, "Failed to download file", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"url": url,
|
|
})
|
|
return ""
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
logger.ErrorCF(opts.LoggerPrefix, "File download returned non-200 status", map[string]interface{}{
|
|
"status": resp.StatusCode,
|
|
"url": url,
|
|
})
|
|
return ""
|
|
}
|
|
|
|
out, err := os.Create(localPath)
|
|
if err != nil {
|
|
logger.ErrorCF(opts.LoggerPrefix, "Failed to create local file", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return ""
|
|
}
|
|
defer out.Close()
|
|
|
|
if _, err := io.Copy(out, resp.Body); err != nil {
|
|
out.Close()
|
|
os.Remove(localPath)
|
|
logger.ErrorCF(opts.LoggerPrefix, "Failed to write file", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return ""
|
|
}
|
|
|
|
logger.DebugCF(opts.LoggerPrefix, "File downloaded successfully", map[string]interface{}{
|
|
"path": localPath,
|
|
})
|
|
|
|
return localPath
|
|
}
|
|
|
|
// DownloadFileSimple is a simplified version of DownloadFile without options
|
|
func DownloadFileSimple(url, filename string) string {
|
|
return DownloadFile(url, filename, DownloadOptions{
|
|
LoggerPrefix: "media",
|
|
})
|
|
}
|