|
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 |
|
} |
|
|