package main import ( "encoding/json" "fmt" "net/http" "net/url" ) type MusicAPIResponse struct { Items []struct { Title string `json:"title"` UploaderName string `json:"uploaderName"` Duration int `json:"duration"` Thumbnail string `json:"thumbnail"` URL string `json:"url"` } `json:"items"` // Removed VideoID since we'll parse from URL } func SearchMusicViaPiped(query string, page int) ([]MusicResult, error) { var lastError error // We will try to use preferred instance mu.Lock() instance := preferredInstance mu.Unlock() if instance != "" && !disabledInstances[instance] { url := fmt.Sprintf( "https://%s/search?q=%s&filter=music_songs&page=%d", instance, url.QueryEscape(query), page, ) resp, err := http.Get(url) if err == nil && resp.StatusCode == http.StatusOK { defer resp.Body.Close() var apiResp MusicAPIResponse if err := json.NewDecoder(resp.Body).Decode(&apiResp); err == nil { return convertPipedToMusicResults(instance, apiResp), nil } } printWarn("Preferred instance %s failed for music, falling back", instance) disableInstance(instance) } // 2. Fallback using others mu.Lock() defer mu.Unlock() for _, inst := range pipedInstances { if disabledInstances[inst] { continue } url := fmt.Sprintf( "https://%s/search?q=%s&filter=music_songs&page=%d", inst, url.QueryEscape(query), page, ) resp, err := http.Get(url) if err != nil || resp.StatusCode != http.StatusOK { printInfo("Disabling instance %s due to error: %v", inst, err) disabledInstances[inst] = true lastError = fmt.Errorf("request to %s failed: %w", inst, err) continue } defer resp.Body.Close() var apiResp MusicAPIResponse if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { lastError = fmt.Errorf("failed to decode response from %s: %w", inst, err) continue } preferredInstance = inst return convertPipedToMusicResults(inst, apiResp), nil } return nil, fmt.Errorf("all Piped instances failed, last error: %v", lastError) } func convertPipedToMusicResults(instance string, resp MusicAPIResponse) []MusicResult { seen := make(map[string]bool) var results []MusicResult for _, item := range resp.Items { // Extract video ID from URL u, err := url.Parse(item.URL) if err != nil { continue } videoID := u.Query().Get("v") if videoID == "" || seen[videoID] { continue } seen[videoID] = true results = append(results, MusicResult{ Title: item.Title, Artist: item.UploaderName, URL: fmt.Sprintf("https://music.youtube.com%s", item.URL), Duration: formatDuration(item.Duration), Thumbnail: item.Thumbnail, Source: "YouTube Music", //AudioURL: fmt.Sprintf("https://%s/stream/%s", instance, videoID), }) } return results }