|
package netease_music |
|
|
|
import ( |
|
"io" |
|
"net/http" |
|
"path" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/alist-org/alist/v3/drivers/base" |
|
"github.com/alist-org/alist/v3/internal/errs" |
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
) |
|
|
|
func (d *NeteaseMusic) request(url, method string, opt ReqOption) ([]byte, error) { |
|
req := base.RestyClient.R() |
|
|
|
req.SetHeader("Cookie", d.Addition.Cookie) |
|
|
|
if strings.Contains(url, "music.163.com") { |
|
req.SetHeader("Referer", "https://music.163.com") |
|
} |
|
|
|
if opt.cookies != nil { |
|
for _, cookie := range opt.cookies { |
|
req.SetCookie(cookie) |
|
} |
|
} |
|
|
|
if opt.headers != nil { |
|
for header, value := range opt.headers { |
|
req.SetHeader(header, value) |
|
} |
|
} |
|
|
|
data := opt.data |
|
if opt.crypto == "weapi" { |
|
data = weapi(data) |
|
re, _ := regexp.Compile(`/\w*api/`) |
|
url = re.ReplaceAllString(url, "/weapi/") |
|
} else if opt.crypto == "eapi" { |
|
ch := new(Characteristic).fromDriver(d) |
|
req.SetCookies(ch.toCookies()) |
|
data = eapi(opt.url, ch.merge(data)) |
|
re, _ := regexp.Compile(`/\w*api/`) |
|
url = re.ReplaceAllString(url, "/eapi/") |
|
} else if opt.crypto == "linuxapi" { |
|
re, _ := regexp.Compile(`/\w*api/`) |
|
data = linuxapi(map[string]interface{}{ |
|
"url": re.ReplaceAllString(url, "/api/"), |
|
"method": method, |
|
"params": data, |
|
}) |
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36") |
|
url = "https://music.163.com/api/linux/forward" |
|
} |
|
|
|
if method == http.MethodPost { |
|
if opt.stream != nil { |
|
req.SetContentLength(true) |
|
req.SetBody(io.ReadCloser(opt.stream)) |
|
} else { |
|
req.SetFormData(data) |
|
} |
|
res, err := req.Post(url) |
|
return res.Body(), err |
|
} |
|
|
|
if method == http.MethodGet { |
|
res, err := req.Get(url) |
|
return res.Body(), err |
|
} |
|
|
|
return nil, errs.NotImplement |
|
} |
|
|
|
func (d *NeteaseMusic) getSongObjs(args model.ListArgs) ([]model.Obj, error) { |
|
body, err := d.request("https://music.163.com/weapi/v1/cloud/get", http.MethodPost, ReqOption{ |
|
crypto: "weapi", |
|
data: map[string]string{ |
|
"limit": strconv.FormatUint(d.Addition.SongLimit, 10), |
|
"offset": "0", |
|
}, |
|
cookies: []*http.Cookie{ |
|
{Name: "os", Value: "pc"}, |
|
}, |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp ListResp |
|
err = utils.Json.Unmarshal(body, &resp) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
d.fileMapByName = make(map[string]model.Obj) |
|
files := make([]model.Obj, 0, len(resp.Data)) |
|
for _, f := range resp.Data { |
|
song := &model.ObjThumb{ |
|
Object: model.Object{ |
|
IsFolder: false, |
|
Size: f.FileSize, |
|
Name: f.FileName, |
|
Modified: time.UnixMilli(f.AddTime), |
|
ID: strconv.FormatInt(f.SongId, 10), |
|
}, |
|
Thumbnail: model.Thumbnail{Thumbnail: f.SimpleSong.Al.PicUrl}, |
|
} |
|
d.fileMapByName[song.Name] = song |
|
files = append(files, song) |
|
|
|
|
|
lrcName := strings.Split(f.FileName, ".")[0] + ".lrc" |
|
lrc := &model.Object{ |
|
IsFolder: false, |
|
Name: lrcName, |
|
Path: path.Join(args.ReqPath, lrcName), |
|
ID: strconv.FormatInt(f.SongId, 10), |
|
} |
|
d.fileMapByName[lrc.Name] = lrc |
|
} |
|
|
|
return files, nil |
|
} |
|
|
|
func (d *NeteaseMusic) getSongLink(file model.Obj) (*model.Link, error) { |
|
body, err := d.request( |
|
"https://music.163.com/api/song/enhance/player/url", http.MethodPost, ReqOption{ |
|
crypto: "linuxapi", |
|
data: map[string]string{ |
|
"ids": "[" + file.GetID() + "]", |
|
"br": "999000", |
|
}, |
|
cookies: []*http.Cookie{ |
|
{Name: "os", Value: "pc"}, |
|
}, |
|
}, |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp SongResp |
|
err = utils.Json.Unmarshal(body, &resp) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if len(resp.Data) < 1 { |
|
return nil, errs.ObjectNotFound |
|
} |
|
|
|
return &model.Link{URL: resp.Data[0].Url}, nil |
|
} |
|
|
|
func (d *NeteaseMusic) getLyricObj(file model.Obj) (model.Obj, error) { |
|
if lrc, ok := file.(*LyricObj); ok { |
|
return lrc, nil |
|
} |
|
|
|
body, err := d.request( |
|
"https://music.163.com/api/song/lyric?_nmclfl=1", http.MethodPost, ReqOption{ |
|
data: map[string]string{ |
|
"id": file.GetID(), |
|
"tv": "-1", |
|
"lv": "-1", |
|
"rv": "-1", |
|
"kv": "-1", |
|
}, |
|
cookies: []*http.Cookie{ |
|
{Name: "os", Value: "ios"}, |
|
}, |
|
}, |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
lyric := utils.Json.Get(body, "lrc", "lyric").ToString() |
|
|
|
return &LyricObj{ |
|
lyric: lyric, |
|
Object: model.Object{ |
|
IsFolder: false, |
|
ID: file.GetID(), |
|
Name: file.GetName(), |
|
Path: file.GetPath(), |
|
Size: int64(len(lyric)), |
|
}, |
|
}, nil |
|
} |
|
|
|
func (d *NeteaseMusic) removeSongObj(file model.Obj) error { |
|
_, err := d.request("http://music.163.com/weapi/cloud/del", http.MethodPost, ReqOption{ |
|
crypto: "weapi", |
|
data: map[string]string{ |
|
"songIds": "[" + file.GetID() + "]", |
|
}, |
|
}) |
|
|
|
return err |
|
} |
|
|
|
func (d *NeteaseMusic) putSongStream(stream model.FileStreamer) error { |
|
tmp, err := stream.CacheFullInTempFile() |
|
if err != nil { |
|
return err |
|
} |
|
defer tmp.Close() |
|
|
|
u := uploader{driver: d, file: tmp} |
|
|
|
err = u.init(stream) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = u.checkIfExisted() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
token, err := u.allocToken() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if u.meta.needUpload { |
|
err = u.upload(stream) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
|
|
err = u.publishInfo(token.resourceId) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|