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 mu.Lock() defer mu.Unlock() for _, instance := range pipedInstances { if disabledInstances[instance] { continue } 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 { printInfo("Disabling instance %s due to error: %v", instance, err) disabledInstances[instance] = true lastError = fmt.Errorf("request to %s failed: %w", instance, 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", instance, err) continue } return convertPipedToMusicResults(instance, 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 }