diff --git a/providers/kugou/kugou.go b/providers/kugou/kugou.go index bd5f15a..474aa98 100644 --- a/providers/kugou/kugou.go +++ b/providers/kugou/kugou.go @@ -213,7 +213,7 @@ func (k *Kugou) GetMediaUrl(meta miaosic.MetaData, quality miaosic.Quality) ([]m if err != nil { return nil, err } - fmt.Println(urlResp.String()) + //fmt.Println(urlResp.String()) urlJson := gjson.ParseBytes(urlResp.Body()) if !urlJson.Get("url").Exists() { return nil, errors.New("kugou: failed to get media url, might be vip only") diff --git a/providers/kugou/kugou_instr.go b/providers/kugou/kugou_instrumental.go similarity index 100% rename from providers/kugou/kugou_instr.go rename to providers/kugou/kugou_instrumental.go diff --git a/providers/kugou/playlist.go b/providers/kugou/playlist.go index ccd680b..b5f3d15 100644 --- a/providers/kugou/playlist.go +++ b/providers/kugou/playlist.go @@ -1,11 +1,138 @@ package kugou -import "github.com/AynaLivePlayer/miaosic" +import ( + "encoding/json" + "errors" + "fmt" + "github.com/AynaLivePlayer/miaosic" + "github.com/aynakeya/deepcolor" + "github.com/tidwall/gjson" + "net/http" + "strings" +) func (k *Kugou) MatchPlaylist(uri string) (miaosic.MetaData, bool) { return miaosic.MetaData{}, false } -func (k *Kugou) GetPlaylist(meta miaosic.MetaData) (*miaosic.Playlist, error) { - return nil, miaosic.ErrNotImplemented +func (k *Kugou) getCollectionId(gcid string) (string, error) { + data := map[string]interface{}{ + "data": []map[string]interface{}{ + { + "id": gcid, "id_type": "1", + }, + }, + "ret_info": 1, + } + dataBytes, _ := json.Marshal(data) + param := k.addAndroidParams(map[string]interface{}{}, string(dataBytes)) + urlReq, _ := deepcolor.NewGetRequestWithQuery( + "https://t.kugou.com/v1/songlist/batch_decode", + param, map[string]string{}, + ) + urlReq.Method = http.MethodPost + urlReq.Data = dataBytes + resp, err := miaosic.Requester.HTTP(urlReq) + if err != nil { + return "", err + } + collId := gjson.Get(resp.String(), "data.list.0.global_collection_id").String() + if collId == "" { + return "", fmt.Errorf("kugou: failed to get collection id") + } + return collId, nil +} + +func (k *Kugou) getPlaylistTitle(collId string) (string, error) { + data := map[string]interface{}{ + "data": []map[string]interface{}{ + { + "global_collection_id": collId, + }, + }, + "userid": "0", + "token": "", + } + dataBytes, _ := json.Marshal(data) + param := k.addAndroidParams(map[string]interface{}{}, string(dataBytes)) + urlReq, _ := deepcolor.NewGetRequestWithQuery( + "https://gateway.kugou.com/v3/get_list_info", + param, map[string]string{ + "x-router": "pubsongs.kugou.com", + }, + ) + urlReq.Method = http.MethodPost + urlReq.Data = dataBytes + resp, err := miaosic.Requester.HTTP(urlReq) + if err != nil { + return "", err + } + title := gjson.Get(resp.String(), "data.0.name").String() + if title == "" { + return "", fmt.Errorf("kugou: failed to get playlist title") + } + return title, nil +} + +func (k *Kugou) GetPlaylist(meta miaosic.MetaData) (*miaosic.Playlist, error) { + collId := meta.Identifier + if !strings.HasPrefix(collId, "collection_") { + var err error + collId, err = k.getCollectionId(collId) + if err != nil { + return nil, err + } + } + params := map[string]interface{}{ + "global_collection_id": collId, + "pagesize": 100, + "plat": 1, + "type": 1, + "area_code": 1, + "begin_idx": 0, + } + playlist := &miaosic.Playlist{ + Meta: meta, + Title: "Kugou Collection " + collId, + Medias: make([]miaosic.MediaInfo, 0), + } + title, err := k.getPlaylistTitle(collId) + if err == nil { + playlist.Title = title + } + for page := 0; page < 25; page++ { + params["begin_idx"] = page * 100 + params = k.addAndroidParams(params, "") + urlReq, _ := deepcolor.NewGetRequestWithQuery( + "https://gateway.kugou.com/pubsongs/v2/get_other_list_file_nofilt", + params, map[string]string{}, + ) + resp, err := miaosic.Requester.HTTP(urlReq) + if err != nil { + return nil, err + } + result := gjson.ParseBytes(resp.Body()) + if result.Get("error_code").Int() != 0 { + return nil, errors.New("kugou: get playlist error") + } + count := int(result.Get("data.count").Int()) + medias := result.Get("data.songs") + medias.ForEach(func(key, value gjson.Result) bool { + playlist.Medias = append(playlist.Medias, miaosic.MediaInfo{ + Title: value.Get("name").String(), + Cover: miaosic.Picture{Url: strings.Replace(value.Get("cover").String(), "{size}", "128", 1)}, + Artist: value.Get("singerinfo.0.name").String(), + Album: value.Get("albuminfo.name").String(), + Meta: miaosic.MetaData{ + Provider: k.GetName(), + Identifier: value.Get("hash").String(), + }, + }) + return true + }) + if page*100+100 >= count { + break + } + } + return playlist, nil } diff --git a/providers/kugou/playlist_test.go b/providers/kugou/playlist_test.go new file mode 100644 index 0000000..5a5f088 --- /dev/null +++ b/providers/kugou/playlist_test.go @@ -0,0 +1,26 @@ +package kugou + +import ( + "fmt" + "github.com/AynaLivePlayer/miaosic" + "github.com/stretchr/testify/require" + "testing" +) + +func TestKugou_GetPlaylist(t *testing.T) { + playlist, err := testApi.GetPlaylist(miaosic.MetaData{Identifier: "gcid_3zfcfgjcz31z06d"}) + require.NoError(t, err) + fmt.Println(playlist.Medias) +} + +func TestKugou_getCollectionId(t *testing.T) { + val, err := testApi.getCollectionId("gcid_3zfcfgjcz31z06d") + require.NoError(t, err) + require.Equal(t, "collection_3_806499027_106_0", val) +} + +func TestKugou_getPlaylistTitle(t *testing.T) { + val, err := testApi.getPlaylistTitle("collection_3_806499027_106_0") + require.NoError(t, err) + require.Equal(t, "emo伤感天花板|来自0.8×的孤独与失恋", val) +} diff --git a/providers/kugou/readme.txt b/providers/kugou/readme.txt new file mode 100644 index 0000000..1b4d459 --- /dev/null +++ b/providers/kugou/readme.txt @@ -0,0 +1,5 @@ +part of code is inspired by + +https://github.com/MakcRe/KuGouMusicApi + +under MIT License \ No newline at end of file diff --git a/providers/kugou/utils.go b/providers/kugou/utils.go index fe0d0c2..5d24089 100644 --- a/providers/kugou/utils.go +++ b/providers/kugou/utils.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" "sort" - "strconv" "strings" + "time" ) const ( @@ -26,12 +26,6 @@ func signKey(appid string, hash, mid, userid string) string { return getMD5Hash(data) } -// signParamsKey encrypts the given parameters and returns the encrypted signParams. -func signParamsKey(data int64) string { - dataStr := appid + signkeyLite + clientver + strconv.FormatInt(data, 10) - return getMD5Hash(dataStr) -} - func signatureAndroidParams(signkey string, params map[string]interface{}, data string) string { // Sort the keys of the params map keys := make([]string, 0, len(params)) @@ -56,7 +50,7 @@ func signatureAndroidParams(signkey string, params map[string]interface{}, data } // Generate the MD5 hash - hash := md5.Sum([]byte(signkey + paramsString.String() + string(data) + signkey)) + hash := md5.Sum([]byte(signkey + paramsString.String() + data + signkey)) return hex.EncodeToString(hash[:]) } @@ -81,3 +75,24 @@ func signatureWebParams(params map[string]string) string { hash := md5.Sum([]byte(str + paramsString.String() + str)) return strings.ToUpper(hex.EncodeToString(hash[:])) } + +func (k *Kugou) addAndroidParams(params map[string]interface{}, data string) map[string]interface{} { + if token, ok := k.cookie["token"]; ok { + params["token"] = token + } else { + params["token"] = "" + } + if userId, ok := k.cookie["userid"]; ok { + params["userid"] = userId + } else { + params["userid"] = "0" + } + params["appid"] = k.appid + params["clientver"] = k.clientver + params["dfid"] = k.dfid + params["mid"] = getMD5Hash(k.dfid) + params["uuid"] = getMD5Hash(k.dfid) + params["clienttime"] = fmt.Sprintf("%d", time.Now().Unix()) + params["signature"] = signatureAndroidParams(k.signkey, params, data) + return params +}