package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) var httpClient *http.Client func initHTTPClient() { httpClient = &http.Client{Timeout: time.Duration(cfg.HTTPTimeout) * time.Second} } func postInnerTube(endpoint string, extraFields map[string]any) ([]byte, error) { body := map[string]any{ "context": map[string]any{ "client": map[string]any{ "clientName": "WEB_REMIX", "clientVersion": "1.20260225.01.00", }, }, } for k, v := range extraFields { body[k] = v } data, err := json.Marshal(body) if err != nil { return nil, err } url := InnerTubeBase + "/" + endpoint + "?prettyPrint=false" resp, err := httpClient.Post(url, "application/json", bytes.NewReader(data)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d from %s", resp.StatusCode, endpoint) } return io.ReadAll(resp.Body) } func SearchArtists(query string) ([]Artist, error) { data, err := postInnerTube("search", map[string]any{ "query": query, "params": "EgWKAQIIAWoKEAMQBBAJEAoQBQ==", }) if err != nil { return nil, err } ids := reChannelID.FindAllString(string(data), -1) seen := make(map[string]bool) var unique []string for _, id := range ids { if !seen[id] { seen[id] = true unique = append(unique, id) } if len(unique) >= cfg.MaxArtistResults { break } } var artists []Artist for _, id := range unique { name, mpreIDs, err := BrowseChannel(id) if err != nil { continue } if len(mpreIDs) == 0 { continue } artists = append(artists, Artist{ ID: id, Name: name, ReleaseCount: len(mpreIDs), }) } return artists, nil } func BrowseChannel(channelID string) (string, []string, error) { data, err := postInnerTube("browse", map[string]any{ "browseId": channelID, }) if err != nil { return "", nil, err } text := string(data) name := "" if loc := reHeaderTag.FindStringIndex(text); loc != nil { // Extract first "text":"..." after musicImmersiveHeaderRenderer if m := reTitle.FindStringSubmatch(text[loc[1]:]); m != nil { name = strings.TrimSpace(m[1]) } } if name == "" { // Fallback to first text match if m := reTitle.FindStringSubmatch(text); m != nil { name = m[1] } } mpreIDs := reMPREID.FindAllString(text, -1) seen := make(map[string]bool) var unique []string for _, id := range mpreIDs { if !seen[id] { seen[id] = true unique = append(unique, id) } } return name, unique, nil } func ResolveMPRE(mpreID string) (Release, error) { data, err := postInnerTube("browse", map[string]any{ "browseId": mpreID, }) if err != nil { return Release{}, err } text := string(data) olakID := "" if m := reOLAKID.FindString(text); m != "" { olakID = m } if olakID == "" { return Release{}, fmt.Errorf("no OLAK ID found for %s", mpreID) } albumTitle := "" if m := reTitle.FindStringSubmatch(text); m != nil { albumTitle = m[1] } artist := "" if m := reStrapline.FindStringSubmatch(text); m != nil { artist = strings.TrimSpace(m[1]) } return Release{ MPREID: mpreID, OLAKID: olakID, Title: albumTitle, Artist: artist, }, nil } func SearchAlbums(query string) ([]Release, error) { data, err := postInnerTube("search", map[string]any{ "query": query, "params": "EgWKAQIYAWoKEAMQBBAJEAoQBQ==", }) if err != nil { return nil, err } ids := reMPREID.FindAllString(string(data), -1) seen := make(map[string]bool) var unique []string for _, id := range ids { if !seen[id] { seen[id] = true unique = append(unique, id) } if len(unique) >= cfg.MaxAlbumResults { break } } return ResolveAllReleases(unique), nil } func ResolveAllReleases(mpreIDs []string) []Release { var releases []Release for _, id := range mpreIDs { r, err := ResolveMPRE(id) if err != nil { continue } releases = append(releases, r) } return releases }