diff --git a/main.go b/main.go index 6ec333a..dbf4394 100644 --- a/main.go +++ b/main.go @@ -778,7 +778,7 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) { counter.Error++ return } - _, err := runv3.Run(track.ID, trackPath, token, mediaUserToken, false) + _, err := runv3.Run(track.ID, trackPath, token, mediaUserToken, false, "") if err != nil { fmt.Println("Failed to dl aac-lc:", err) if err.Error() == "Unavailable" { @@ -962,14 +962,14 @@ func ripStation(albumId string, token string, storefront string, mediaUserToken fmt.Println("Radio already exists locally.") return nil } - assetsUrl, err := ampapi.GetStationAssetsUrl(station.ID, mediaUserToken, token) + assetsUrl, serverUrl, err := ampapi.GetStationAssetsUrlAndServerUrl(station.ID, mediaUserToken, token) if err != nil { fmt.Println("Failed to get station assets url.", err) counter.Error++ return err } trackM3U8 := strings.ReplaceAll(assetsUrl, "index.m3u8", "256/prog_index.m3u8") - keyAndUrls, _ := runv3.Run(station.ID, trackM3U8, token, mediaUserToken, true) + keyAndUrls, _ := runv3.Run(station.ID, trackM3U8, token, mediaUserToken, true, serverUrl) err = runv3.ExtMvData(keyAndUrls, trackPath) if err != nil { fmt.Println("Failed to download station stream.", err) @@ -1849,17 +1849,17 @@ func mvDownloader(adamID string, saveDir string, token string, storefront string return nil } - mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true) + mvm3u8url, _, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true) if mvm3u8url == "" { return errors.New("media-user-token may wrong or expired") } os.MkdirAll(saveDir, os.ModePerm) videom3u8url, _ := extractVideo(mvm3u8url) - videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true) + videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true, "") _ = runv3.ExtMvData(videokeyAndUrls, vidPath) audiom3u8url, _ := extractMvAudio(mvm3u8url) - audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true) + audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true, "") _ = runv3.ExtMvData(audiokeyAndUrls, audPath) tags := []string{ diff --git a/utils/ampapi/station.go b/utils/ampapi/station.go index acb91b2..8ea7876 100644 --- a/utils/ampapi/station.go +++ b/utils/ampapi/station.go @@ -45,18 +45,18 @@ func GetStationResp(storefront string, id string, language string, token string) return obj, nil } -func GetStationAssetsUrl(id string, mutoken string, token string) (string, error) { +func GetStationAssetsUrlAndServerUrl(id string, mutoken string, token string) (string, string, error) { var err error if token == "" { token, err = GetToken() if err != nil { - return "", err + return "", "", err } } req, err := http.NewRequest("GET", "https://amp-api.music.apple.com/v1/play/assets", nil) if err != nil { - return "", err + return "", "", err } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") @@ -71,18 +71,18 @@ func GetStationAssetsUrl(id string, mutoken string, token string) (string, error req.URL.RawQuery = query.Encode() do, err := http.DefaultClient.Do(req) if err != nil { - return "", err + return "", "", err } defer do.Body.Close() if do.StatusCode != http.StatusOK { - return "", errors.New(do.Status) + return "", "", errors.New(do.Status) } obj := new(StationAssets) err = json.NewDecoder(do.Body).Decode(&obj) if err != nil { - return "", err + return "", "", err } - return obj.Results.Assets[0].Url, nil + return obj.Results.Assets[0].Url, obj.Results.Assets[0].KeyServerUrl, nil } func GetStationNextTracks(id, mutoken, language, token string) (*TrackResp, error) { diff --git a/utils/runv3/runv3.go b/utils/runv3/runv3.go index e9bfdfb..a2bc991 100644 --- a/utils/runv3/runv3.go +++ b/utils/runv3/runv3.go @@ -64,7 +64,7 @@ func BeforeRequest(cl *resty.Client, ctx context.Context, url string, body []byt jsondata := map[string]interface{}{ "challenge": base64.StdEncoding.EncodeToString(body), // 'body' is passed in directly "key-system": "com.widevine.alpha", - "uri": "data:;base64," + ctx.Value("pssh").(string), + "uri": ctx.Value("uriPrefix").(string) + "," + ctx.Value("pssh").(string), "adamId": ctx.Value("adamId").(string), "isLibrary": false, "user-initiated": true, @@ -102,7 +102,7 @@ func AfterRequest(response *resty.Response) ([]byte, error) { return license, nil } -func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool) (string, string, error) { +func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool) (string, string, string, error) { url := "https://play.music.apple.com/WebObjects/MZPlay.woa/wa/webPlayback" postData := map[string]string{ "salableAdamId": adamId, @@ -110,12 +110,12 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool jsonData, err := json.Marshal(postData) if err != nil { fmt.Println("Error encoding JSON:", err) - return "", "", err + return "", "", "", err } req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonData))) if err != nil { fmt.Println("Error creating request:", err) - return "", "", err + return "", "", "", err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Origin", "https://music.apple.com") @@ -130,7 +130,7 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool //resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) - return "", "", err + return "", "", "", err } defer resp.Body.Close() //fmt.Println("Response Status:", resp.Status) @@ -138,25 +138,25 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool err = json.NewDecoder(resp.Body).Decode(&obj) if err != nil { fmt.Println("json err:", err) - return "", "", err + return "", "", "", err } if len(obj.List) > 0 { if mvmode { - return obj.List[0].HlsPlaylistUrl, "", nil + return obj.List[0].HlsPlaylistUrl, "", "", nil } // 遍历 Assets for i := range obj.List[0].Assets { if obj.List[0].Assets[i].Flavor == "28:ctrp256" { - kidBase64, fileurl, err := extractKidBase64(obj.List[0].Assets[i].URL, false) + kidBase64, fileurl, uriPrefix, err := extractKidBase64(obj.List[0].Assets[i].URL, false) if err != nil { - return "", "", err + return "", "", "", err } - return fileurl, kidBase64, nil + return fileurl, kidBase64, uriPrefix, nil } continue } } - return "", "", errors.New("Unavailable") + return "", "", "", errors.New("Unavailable") } type Songlist struct { @@ -171,30 +171,32 @@ type Songlist struct { Status int `json:"status"` } -func extractKidBase64(b string, mvmode bool) (string, string, error) { +func extractKidBase64(b string, mvmode bool) (string, string, string, error) { resp, err := http.Get(b) if err != nil { - return "", "", err + return "", "", "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return "", "", errors.New(resp.Status) + return "", "", "", errors.New(resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { - return "", "", err + return "", "", "", err } masterString := string(body) from, listType, err := m3u8.DecodeFrom(strings.NewReader(masterString), true) if err != nil { - return "", "", err + return "", "", "", err } var kidbase64 string + var uriPrefix string var urlBuilder strings.Builder if listType == m3u8.MEDIA { mediaPlaylist := from.(*m3u8.MediaPlaylist) if mediaPlaylist.Key != nil { split := strings.Split(mediaPlaylist.Key.URI, ",") + uriPrefix = split[0] kidbase64 = split[1] lastSlashIndex := strings.LastIndex(b, "/") // 截取最后一个斜杠之前的部分 @@ -221,7 +223,7 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) { } else { fmt.Println("Not a media playlist") } - return kidbase64, urlBuilder.String(), nil + return kidbase64, urlBuilder.String(), uriPrefix, nil } func extsong(b string) bytes.Buffer { resp, err := http.Get(b) @@ -251,18 +253,19 @@ func extsong(b string) bytes.Buffer { io.Copy(io.MultiWriter(&buffer, bar), resp.Body) return buffer } -func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool) (string, error) { +func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool, serverUrl string) (string, error) { var keystr string //for mv key var fileurl string var kidBase64 string + var uriPrefix string var err error if mvmode { - kidBase64, fileurl, err = extractKidBase64(trackpath, true) + kidBase64, fileurl, uriPrefix, err = extractKidBase64(trackpath, true) if err != nil { return "", err } } else { - fileurl, kidBase64, err = GetWebplayback(adamId, authtoken, mutoken, false) + fileurl, kidBase64, uriPrefix, err = GetWebplayback(adamId, authtoken, mutoken, false) if err != nil { return "", err } @@ -270,6 +273,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo ctx := context.Background() ctx = context.WithValue(ctx, "pssh", kidBase64) ctx = context.WithValue(ctx, "adamId", adamId) + ctx = context.WithValue(ctx, "uriPrefix", uriPrefix) pssh, err := getPSSH("", kidBase64) //fmt.Println(pssh) if err != nil { @@ -289,8 +293,8 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo } key.CdmInit() var keybt []byte - if strings.Contains(adamId, "ra.") { - keystr, keybt, err = key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/web/radio/versions/1/license", pssh, nil) + if serverUrl != "" { + keystr, keybt, err = key.GetKey(ctx, serverUrl, pssh, nil) if err != nil { fmt.Println(err) return "", err