From f261ef50ccad6e7ba1a8015503cccae49fb1c253 Mon Sep 17 00:00:00 2001 From: liuycy Date: Thu, 9 May 2024 14:29:35 +0800 Subject: [PATCH] feat: add supports for netease music driver (#6423 close #5364) --- drivers/all.go | 1 + drivers/netease_music/crypto.go | 135 ++++++++++++++++++ drivers/netease_music/driver.go | 110 ++++++++++++++ drivers/netease_music/meta.go | 32 +++++ drivers/netease_music/types.go | 116 +++++++++++++++ drivers/netease_music/upload.go | 208 +++++++++++++++++++++++++++ drivers/netease_music/util.go | 246 ++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 9 files changed, 851 insertions(+) create mode 100644 drivers/netease_music/crypto.go create mode 100644 drivers/netease_music/driver.go create mode 100644 drivers/netease_music/meta.go create mode 100644 drivers/netease_music/types.go create mode 100644 drivers/netease_music/upload.go create mode 100644 drivers/netease_music/util.go diff --git a/drivers/all.go b/drivers/all.go index 08d8f1cb..d1d5f84a 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -32,6 +32,7 @@ import ( _ "github.com/alist-org/alist/v3/drivers/mediatrack" _ "github.com/alist-org/alist/v3/drivers/mega" _ "github.com/alist-org/alist/v3/drivers/mopan" + _ "github.com/alist-org/alist/v3/drivers/netease_music" _ "github.com/alist-org/alist/v3/drivers/onedrive" _ "github.com/alist-org/alist/v3/drivers/onedrive_app" _ "github.com/alist-org/alist/v3/drivers/pikpak" diff --git a/drivers/netease_music/crypto.go b/drivers/netease_music/crypto.go new file mode 100644 index 00000000..76ff6548 --- /dev/null +++ b/drivers/netease_music/crypto.go @@ -0,0 +1,135 @@ +package netease_music + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/pem" + "math/big" + "strings" + + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/alist-org/alist/v3/pkg/utils/random" +) + +var ( + linuxapiKey = []byte("rFgB&h#%2?^eDg:Q") + eapiKey = []byte("e82ckenh8dichen8") + iv = []byte("0102030405060708") + presetKey = []byte("0CoJUm6Qyw8W8jud") + publicKey = []byte("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----") + stdChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") +) + +func aesKeyPending(key []byte) []byte { + k := len(key) + count := 0 + switch true { + case k <= 16: + count = 16 - k + case k <= 24: + count = 24 - k + case k <= 32: + count = 32 - k + default: + return key[:32] + } + if count == 0 { + return key + } + + return append(key, bytes.Repeat([]byte{0}, count)...) +} + +func pkcs7Padding(src []byte, blockSize int) []byte { + padding := blockSize - len(src)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +func aesCBCEncrypt(src, key, iv []byte) []byte { + block, _ := aes.NewCipher(aesKeyPending(key)) + src = pkcs7Padding(src, block.BlockSize()) + dst := make([]byte, len(src)) + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(dst, src) + + return dst +} + +func aesECBEncrypt(src, key []byte) []byte { + block, _ := aes.NewCipher(aesKeyPending(key)) + + src = pkcs7Padding(src, block.BlockSize()) + dst := make([]byte, len(src)) + + ecbCryptBlocks(block, dst, src) + + return dst +} + +func ecbCryptBlocks(block cipher.Block, dst, src []byte) { + bs := block.BlockSize() + + for len(src) > 0 { + block.Encrypt(dst, src[:bs]) + src = src[bs:] + dst = dst[bs:] + } +} + +func rsaEncrypt(buffer, key []byte) []byte { + buffers := make([]byte, 128-16, 128) + buffers = append(buffers, buffer...) + block, _ := pem.Decode(key) + pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes) + pub := pubInterface.(*rsa.PublicKey) + c := new(big.Int).SetBytes([]byte(buffers)) + return c.Exp(c, big.NewInt(int64(pub.E)), pub.N).Bytes() +} + +func getSecretKey() ([]byte, []byte) { + key := make([]byte, 16) + reversed := make([]byte, 16) + for i := 0; i < 16; i++ { + result := stdChars[random.RangeInt64(0, 62)] + key[i] = result + reversed[15-i] = result + } + return key, reversed +} + +func weapi(data map[string]string) map[string]string { + text, _ := utils.Json.Marshal(data) + secretKey, reversedKey := getSecretKey() + params := []byte(base64.StdEncoding.EncodeToString(aesCBCEncrypt(text, presetKey, iv))) + return map[string]string{ + "params": base64.StdEncoding.EncodeToString(aesCBCEncrypt(params, reversedKey, iv)), + "encSecKey": hex.EncodeToString(rsaEncrypt(secretKey, publicKey)), + } +} + +func eapi(url string, data map[string]interface{}) map[string]string { + text, _ := utils.Json.Marshal(data) + msg := "nobody" + url + "use" + string(text) + "md5forencrypt" + h := md5.New() + h.Write([]byte(msg)) + digest := hex.EncodeToString(h.Sum(nil)) + params := []byte(url + "-36cd479b6b5-" + string(text) + "-36cd479b6b5-" + digest) + return map[string]string{ + "params": hex.EncodeToString(aesECBEncrypt(params, eapiKey)), + } +} + +func linuxapi(data map[string]interface{}) map[string]string { + text, _ := utils.Json.Marshal(data) + return map[string]string{ + "eparams": strings.ToUpper(hex.EncodeToString(aesECBEncrypt(text, linuxapiKey))), + } +} diff --git a/drivers/netease_music/driver.go b/drivers/netease_music/driver.go new file mode 100644 index 00000000..c0d103de --- /dev/null +++ b/drivers/netease_music/driver.go @@ -0,0 +1,110 @@ +package netease_music + +import ( + "context" + "strings" + + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/errs" + "github.com/alist-org/alist/v3/internal/model" + _ "golang.org/x/image/webp" +) + +type NeteaseMusic struct { + model.Storage + Addition + + csrfToken string + musicU string + fileMapByName map[string]model.Obj +} + +func (d *NeteaseMusic) Config() driver.Config { + return config +} + +func (d *NeteaseMusic) GetAddition() driver.Additional { + return &d.Addition +} + +func (d *NeteaseMusic) Init(ctx context.Context) error { + d.csrfToken = d.Addition.getCookie("__csrf") + d.musicU = d.Addition.getCookie("MUSIC_U") + + if d.csrfToken == "" || d.musicU == "" { + return errs.EmptyToken + } + + return nil +} + +func (d *NeteaseMusic) Drop(ctx context.Context) error { + return nil +} + +func (d *NeteaseMusic) Get(ctx context.Context, path string) (model.Obj, error) { + if path == "/" { + return &model.Object{ + IsFolder: true, + Path: path, + }, nil + } + + fragments := strings.Split(path, "/") + if len(fragments) > 1 { + fileName := fragments[1] + if strings.HasSuffix(fileName, ".lrc") { + lrc := d.fileMapByName[fileName] + return d.getLyricObj(lrc) + } + if song, ok := d.fileMapByName[fileName]; ok { + return song, nil + } else { + return nil, errs.ObjectNotFound + } + } + + return nil, errs.ObjectNotFound +} + +func (d *NeteaseMusic) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + return d.getSongObjs(args) +} + +func (d *NeteaseMusic) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + if lrc, ok := file.(*LyricObj); ok { + if args.Type == "parsed" { + return lrc.getLyricLink(), nil + } else { + return lrc.getProxyLink(args), nil + } + } + + return d.getSongLink(file) +} + +func (d *NeteaseMusic) Remove(ctx context.Context, obj model.Obj) error { + return d.removeSongObj(obj) +} + +func (d *NeteaseMusic) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { + return d.putSongStream(stream) +} + +func (d *NeteaseMusic) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + return errs.NotSupport +} + +func (d *NeteaseMusic) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + return errs.NotSupport +} + +func (d *NeteaseMusic) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + return errs.NotSupport +} + +func (d *NeteaseMusic) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + return errs.NotSupport +} + +var _ driver.Driver = (*NeteaseMusic)(nil) diff --git a/drivers/netease_music/meta.go b/drivers/netease_music/meta.go new file mode 100644 index 00000000..8ddfd728 --- /dev/null +++ b/drivers/netease_music/meta.go @@ -0,0 +1,32 @@ +package netease_music + +import ( + "regexp" + + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + Cookie string `json:"cookie" type:"text" required:"true" help:""` + SongLimit uint64 `json:"song_limit" default:"200" type:"number" help:"only get 200 songs by default"` +} + +func (ad *Addition) getCookie(name string) string { + re := regexp.MustCompile(name + "=([^(;|$)]+)") + matches := re.FindStringSubmatch(ad.Cookie) + if len(matches) < 2 { + return "" + } + return matches[1] +} + +var config = driver.Config{ + Name: "NeteaseMusic", +} + +func init() { + op.RegisterDriver(func() driver.Driver { + return &NeteaseMusic{} + }) +} diff --git a/drivers/netease_music/types.go b/drivers/netease_music/types.go new file mode 100644 index 00000000..edbd40ee --- /dev/null +++ b/drivers/netease_music/types.go @@ -0,0 +1,116 @@ +package netease_music + +import ( + "context" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/sign" + "github.com/alist-org/alist/v3/pkg/http_range" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/alist-org/alist/v3/pkg/utils/random" + "github.com/alist-org/alist/v3/server/common" +) + +type HostsResp struct { + Upload []string `json:"upload"` +} + +type SongResp struct { + Data []struct { + Url string `json:"url"` + } `json:"data"` +} + +type ListResp struct { + Size string `json:"size"` + MaxSize string `json:"maxSize"` + Data []struct { + AddTime int64 `json:"addTime"` + FileName string `json:"fileName"` + FileSize int64 `json:"fileSize"` + SongId int64 `json:"songId"` + SimpleSong struct { + Al struct { + PicUrl string `json:"picUrl"` + } `json:"al"` + } `json:"simpleSong"` + } `json:"data"` +} + +type LyricObj struct { + model.Object + lyric string +} + +func (lrc *LyricObj) getProxyLink(args model.LinkArgs) *model.Link { + rawURL := common.GetApiUrl(args.HttpReq) + "/p" + lrc.Path + rawURL = utils.EncodePath(rawURL, true) + "?type=parsed&sign=" + sign.Sign(lrc.Path) + return &model.Link{URL: rawURL} +} + +func (lrc *LyricObj) getLyricLink() *model.Link { + reader := strings.NewReader(lrc.lyric) + return &model.Link{ + RangeReadCloser: &model.RangeReadCloser{ + RangeReader: func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) { + if httpRange.Length < 0 { + return io.NopCloser(reader), nil + } + sr := io.NewSectionReader(reader, httpRange.Start, httpRange.Length) + return io.NopCloser(sr), nil + }, + Closers: utils.EmptyClosers(), + }, + } +} + +type ReqOption struct { + crypto string + stream model.FileStreamer + data map[string]string + headers map[string]string + cookies []*http.Cookie + url string +} + +type Characteristic map[string]string + +func (ch *Characteristic) fromDriver(d *NeteaseMusic) *Characteristic { + *ch = map[string]string{ + "osver": "", + "deviceId": "", + "mobilename": "", + "appver": "6.1.1", + "versioncode": "140", + "buildver": strconv.FormatInt(time.Now().Unix(), 10), + "resolution": "1920x1080", + "os": "android", + "channel": "", + "requestId": strconv.FormatInt(time.Now().Unix()*1000, 10) + strconv.Itoa(int(random.RangeInt64(0, 1000))), + "MUSIC_U": d.musicU, + } + return ch +} + +func (ch Characteristic) toCookies() []*http.Cookie { + cookies := make([]*http.Cookie, 0) + for k, v := range ch { + cookies = append(cookies, &http.Cookie{Name: k, Value: v}) + } + return cookies +} + +func (ch *Characteristic) merge(data map[string]string) map[string]interface{} { + body := map[string]interface{}{ + "header": ch, + } + for k, v := range data { + body[k] = v + } + return body +} diff --git a/drivers/netease_music/upload.go b/drivers/netease_music/upload.go new file mode 100644 index 00000000..ece496b3 --- /dev/null +++ b/drivers/netease_music/upload.go @@ -0,0 +1,208 @@ +package netease_music + +import ( + "crypto/md5" + "encoding/hex" + "io" + "net/http" + "strconv" + "strings" + + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/dhowden/tag" +) + +type token struct { + resourceId string + objectKey string + token string +} + +type songmeta struct { + needUpload bool + songId string + name string + artist string + album string +} + +type uploader struct { + driver *NeteaseMusic + file model.File + meta songmeta + md5 string + ext string + size string + filename string +} + +func (u *uploader) init(stream model.FileStreamer) error { + u.filename = stream.GetName() + u.size = strconv.FormatInt(stream.GetSize(), 10) + + u.ext = "mp3" + if strings.HasSuffix(stream.GetMimetype(), "flac") { + u.ext = "flac" + } + + h := md5.New() + io.Copy(h, stream) + u.md5 = hex.EncodeToString(h.Sum(nil)) + _, err := u.file.Seek(0, io.SeekStart) + if err != nil { + return err + } + + if m, err := tag.ReadFrom(u.file); err != nil { + u.meta = songmeta{} + } else { + u.meta = songmeta{ + name: m.Title(), + artist: m.Artist(), + album: m.Album(), + } + } + if u.meta.name == "" { + u.meta.name = u.filename + } + if u.meta.album == "" { + u.meta.album = "未知专辑" + } + if u.meta.artist == "" { + u.meta.artist = "未知艺术家" + } + _, err = u.file.Seek(0, io.SeekStart) + if err != nil { + return err + } + + return nil +} + +func (u *uploader) checkIfExisted() error { + body, err := u.driver.request("https://interface.music.163.com/api/cloud/upload/check", http.MethodPost, + ReqOption{ + crypto: "weapi", + data: map[string]string{ + "ext": "", + "songId": "0", + "version": "1", + "bitrate": "999000", + "length": u.size, + "md5": u.md5, + }, + cookies: []*http.Cookie{ + {Name: "os", Value: "pc"}, + {Name: "appver", Value: "2.9.7"}, + }, + }, + ) + if err != nil { + return err + } + + u.meta.songId = utils.Json.Get(body, "songId").ToString() + u.meta.needUpload = utils.Json.Get(body, "needUpload").ToBool() + + return nil +} + +func (u *uploader) allocToken(bucket ...string) (token, error) { + if len(bucket) == 0 { + bucket = []string{""} + } + + body, err := u.driver.request("https://music.163.com/weapi/nos/token/alloc", http.MethodPost, ReqOption{ + crypto: "weapi", + data: map[string]string{ + "bucket": bucket[0], + "local": "false", + "type": "audio", + "nos_product": "3", + "filename": u.filename, + "md5": u.md5, + "ext": u.ext, + }, + }) + if err != nil { + return token{}, err + } + + return token{ + resourceId: utils.Json.Get(body, "result", "resourceId").ToString(), + objectKey: utils.Json.Get(body, "result", "objectKey").ToString(), + token: utils.Json.Get(body, "result", "token").ToString(), + }, nil +} + +func (u *uploader) publishInfo(resourceId string) error { + body, err := u.driver.request("https://music.163.com/api/upload/cloud/info/v2", http.MethodPost, ReqOption{ + crypto: "weapi", + data: map[string]string{ + "md5": u.md5, + "filename": u.filename, + "song": u.meta.name, + "album": u.meta.album, + "artist": u.meta.artist, + "songid": u.meta.songId, + "resourceId": resourceId, + "bitrate": "999000", + }, + }) + if err != nil { + return err + } + + _, err = u.driver.request("https://interface.music.163.com/api/cloud/pub/v2", http.MethodPost, ReqOption{ + crypto: "weapi", + data: map[string]string{ + "songid": utils.Json.Get(body, "songId").ToString(), + }, + }) + if err != nil { + return err + } + + return nil +} + +func (u *uploader) upload(stream model.FileStreamer) error { + bucket := "jd-musicrep-privatecloud-audio-public" + token, err := u.allocToken(bucket) + if err != nil { + return err + } + + body, err := u.driver.request("https://wanproxy.127.net/lbs?version=1.0&bucketname="+bucket, http.MethodGet, + ReqOption{}, + ) + if err != nil { + return err + } + var resp HostsResp + err = utils.Json.Unmarshal(body, &resp) + if err != nil { + return err + } + + objectKey := strings.ReplaceAll(token.objectKey, "/", "%2F") + _, err = u.driver.request( + resp.Upload[0]+"/"+bucket+"/"+objectKey+"?offset=0&complete=true&version=1.0", + http.MethodPost, + ReqOption{ + stream: stream, + headers: map[string]string{ + "x-nos-token": token.token, + "Content-Type": "audio/mpeg", + "Content-Length": u.size, + "Content-MD5": u.md5, + }, + }, + ) + if err != nil { + return err + } + + return nil +} diff --git a/drivers/netease_music/util.go b/drivers/netease_music/util.go new file mode 100644 index 00000000..4d0696eb --- /dev/null +++ b/drivers/netease_music/util.go @@ -0,0 +1,246 @@ +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) + + // map song id for lyric + 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 +} diff --git a/go.mod b/go.mod index 11a858a2..a994d768 100644 --- a/go.mod +++ b/go.mod @@ -108,6 +108,7 @@ require ( github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/geoffgarside/ber v1.1.0 // indirect diff --git a/go.sum b/go.sum index 05a9b338..539151e6 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4mA/WVIYtpzVm63vLVAPzJXigg= +github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=