Skip to content

Commit

Permalink
feat: add supports for netease music driver (#6423 close #5364)
Browse files Browse the repository at this point in the history
  • Loading branch information
liuycy authored May 9, 2024
1 parent 7e7b9b9 commit f261ef5
Show file tree
Hide file tree
Showing 9 changed files with 851 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
135 changes: 135 additions & 0 deletions drivers/netease_music/crypto.go
Original file line number Diff line number Diff line change
@@ -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))),
}
}
110 changes: 110 additions & 0 deletions drivers/netease_music/driver.go
Original file line number Diff line number Diff line change
@@ -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)
32 changes: 32 additions & 0 deletions drivers/netease_music/meta.go
Original file line number Diff line number Diff line change
@@ -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{}
})
}
Loading

0 comments on commit f261ef5

Please sign in to comment.