mirror of
https://github.com/ZxwyWebSite/lx-source.git
synced 2025-05-23 21:37:42 +08:00
2024-01-21 v1.0.2-b12-d2
This commit is contained in:
parent
6db6e9eb65
commit
ad7614c564
11
src/env/env.go
vendored
11
src/env/env.go
vendored
@ -69,9 +69,11 @@ type (
|
|||||||
ForceFallback bool `comment:"忽略音质限制,强制获取试听音频"`
|
ForceFallback bool `comment:"忽略音质限制,强制获取试听音频"`
|
||||||
} // `comment:""`
|
} // `comment:""`
|
||||||
Conf_Custom struct {
|
Conf_Custom struct {
|
||||||
// wy (暂未实现)
|
// wy
|
||||||
Wy_Enable bool `comment:"是否开启小芸源"`
|
Wy_Enable bool `comment:"是否开启小芸源"`
|
||||||
Wy_Cookie string `comment:"账号cookie数据"`
|
Wy_Cookie string `comment:"账号cookie数据"`
|
||||||
|
Wy_Refresh_Enable bool `comment:"是否启用刷新登录"`
|
||||||
|
Wy_Refresh_Interval int64 `comment:"下次刷新时间 (由程序维护)"`
|
||||||
|
|
||||||
// mg (暂未实现)
|
// mg (暂未实现)
|
||||||
// Mg_Enable bool `comment:"是否开启小蜜源"`
|
// Mg_Enable bool `comment:"是否开启小蜜源"`
|
||||||
@ -161,7 +163,8 @@ var (
|
|||||||
Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`,
|
Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`,
|
||||||
},
|
},
|
||||||
Custom: Conf_Custom{
|
Custom: Conf_Custom{
|
||||||
Wy_Enable: true,
|
Wy_Enable: true,
|
||||||
|
Wy_Refresh_Interval: 1633622400,
|
||||||
|
|
||||||
Kw_Enable: true,
|
Kw_Enable: true,
|
||||||
Kw_Mode: `kwdes`,
|
Kw_Mode: `kwdes`,
|
||||||
|
@ -33,7 +33,7 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
env.Inits.Add(func() {
|
env.Inits.Add(func() {
|
||||||
if env.Config.Custom.Tx_Refresh_Enable {
|
if env.Config.Custom.Tx_Refresh_Enable {
|
||||||
env.Tasker.Add(`refresh_login`, func(l *logs.Logger) error {
|
env.Tasker.Add(`tx_refresh`, func(l *logs.Logger) error {
|
||||||
refresh(l)
|
refresh(l)
|
||||||
return nil
|
return nil
|
||||||
}, 86000, true)
|
}, 86000, true)
|
||||||
|
@ -4,7 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/rand"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
@ -13,33 +18,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// __all__ = []string{`weEncrypt`, `linuxEncrypt`, `eEncrypt`}
|
ivKey = bytesconv.StringToBytes(`0102030405060708`)
|
||||||
// MODULUS = ztool.Str_FastConcat(
|
presetKey = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`)
|
||||||
// `00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7`,
|
linuxapiKey = bytesconv.StringToBytes(`rFgB&h#%2?^eDg:Q`)
|
||||||
// `b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280`,
|
base62 = bytesconv.StringToBytes(`abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`)
|
||||||
// `104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932`,
|
publicKey = bytesconv.StringToBytes(`-----BEGIN PUBLIC KEY-----
|
||||||
// `575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b`,
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB
|
||||||
// `3ece0462db0a22b8e7`,
|
-----END PUBLIC KEY-----`)
|
||||||
// )
|
|
||||||
// PUBKEY = `010001`
|
|
||||||
// NONCE = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`)
|
|
||||||
// LINUXKEY = bytesconv.StringToBytes(`rFgB&h#%2?^eDg:Q`)
|
|
||||||
eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`)
|
eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`)
|
||||||
ivKey = bytesconv.StringToBytes(`0102030405060708`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func eapiEncrypt(url, text string) map[string][]string {
|
// func eapiEncrypt(url, text string) map[string][]string {
|
||||||
digest := zcypt.CreateMD5(bytesconv.StringToBytes(ztool.Str_FastConcat(
|
// digest := zcypt.CreateMD5(bytesconv.StringToBytes(ztool.Str_FastConcat(
|
||||||
`nobody`, url, `use`, text, `md5forencrypt`,
|
// `nobody`, url, `use`, text, `md5forencrypt`,
|
||||||
)))
|
// )))
|
||||||
data := ztool.Str_FastConcat(
|
// data := ztool.Str_FastConcat(
|
||||||
url, `-36cd479b6b5-`, text, `-36cd479b6b5-`, digest,
|
// url, `-36cd479b6b5-`, text, `-36cd479b6b5-`, digest,
|
||||||
)
|
// )
|
||||||
// 注:JSON编码时会自动将[]byte转为string,这里省去一步转换
|
// // 注:JSON编码时会自动将[]byte转为string,这里省去一步转换
|
||||||
return map[string][]string{
|
// return map[string][]string{
|
||||||
`params`: {bytesconv.BytesToString(aesEncrypt(bytesconv.StringToBytes(data), eapiKey, false))},
|
// `params`: {bytesconv.BytesToString(aesEncrypt(bytesconv.StringToBytes(data), eapiKey, false))},
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// crypto.js
|
// crypto.js
|
||||||
|
|
||||||
@ -64,6 +64,54 @@ func aesEncrypt(text, key []byte, iv bool) []byte {
|
|||||||
return bytes.ToUpper(zcypt.HexEncode(ciphertext))
|
return bytes.ToUpper(zcypt.HexEncode(ciphertext))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname rsaEncryptNone crypto/rsa.encrypt
|
||||||
|
func rsaEncryptNone(*rsa.PublicKey, []byte) ([]byte, error)
|
||||||
|
|
||||||
|
func rsaEncrypt(data []byte) string {
|
||||||
|
pblock, _ := pem.Decode(publicKey)
|
||||||
|
pubKey, _ := x509.ParsePKIXPublicKey(pblock.Bytes)
|
||||||
|
// 注:为实现NONE加密手动导出了标准库里的encrypt方法,若编译不过添加以下代码
|
||||||
|
// /usr/local/go/src/crypto/rsa/rsa.go:478
|
||||||
|
// ```
|
||||||
|
// var Encrypt = encrypt // export
|
||||||
|
// ```
|
||||||
|
// 第二种方式:linkname调用,不用改库 https://www.jianshu.com/p/7b3638b47845
|
||||||
|
encData, err := rsaEncryptNone(pubKey.(*rsa.PublicKey), data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return zcypt.HexToString(encData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func weapi(object map[string]any) map[string][]string {
|
||||||
|
text, err := json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
secretKey := make([]byte, 16)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
secretKey[i] = base62[rand.Intn(62)]
|
||||||
|
}
|
||||||
|
return map[string][]string{
|
||||||
|
`params`: {bytesconv.BytesToString(aesEncrypt(
|
||||||
|
aesEncrypt(text, presetKey, true),
|
||||||
|
secretKey,
|
||||||
|
true,
|
||||||
|
))},
|
||||||
|
`encSecKey`: {rsaEncrypt(ztool.Sort_ReverseNew(secretKey))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func linuxapi(object map[string]any) map[string][]string {
|
||||||
|
text, err := json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return map[string][]string{
|
||||||
|
`eparams`: {bytesconv.BytesToString(aesEncrypt(text, linuxapiKey, false))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func eapi(url string, object map[string]any) map[string][]string {
|
func eapi(url string, object map[string]any) map[string][]string {
|
||||||
text, err := json.Marshal(object)
|
text, err := json.Marshal(object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
61
src/sources/custom/wy/init.go
Normal file
61
src/sources/custom/wy/init.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/cookie"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
刷新登录模块 (来自 NeteaseCloudMusicApi)
|
||||||
|
逻辑:
|
||||||
|
检测返回结果中是否含有"MUSIC_U":
|
||||||
|
如果有则为正常刷新,延时30天
|
||||||
|
否则延时1天
|
||||||
|
注:
|
||||||
|
原代码未提供详细描述,无法确定有效结果判断条件,暂时先这么写
|
||||||
|
*/
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
if env.Config.Custom.Wy_Refresh_Enable && env.Config.Custom.Wy_Cookie != `` {
|
||||||
|
env.Tasker.Add(`wy_refresh`, func(loger *logs.Logger) error {
|
||||||
|
// 前置检测
|
||||||
|
now := time.Now().Unix()
|
||||||
|
if now < env.Config.Custom.Wy_Refresh_Interval {
|
||||||
|
loger.Debug(`Key未过期,跳过...`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 刷新逻辑
|
||||||
|
cookies := cookie.ToMap(cookie.Parse(env.Config.Custom.Wy_Cookie))
|
||||||
|
res, err := LoginRefresh(ReqQuery{
|
||||||
|
Cookie: cookies,
|
||||||
|
})
|
||||||
|
loger.Debug(`Resp: %+v`, res)
|
||||||
|
if err == nil {
|
||||||
|
if out, ok := res.Body[`cookie`].(string); ok {
|
||||||
|
loger.Info(`获取数据成功`)
|
||||||
|
cmap := cookie.ToMap(cookie.Parse(out))
|
||||||
|
maps.Copy(cookies, cmap)
|
||||||
|
env.Config.Custom.Wy_Cookie = cookie.Marshal(cookies)
|
||||||
|
loger.Debug(`Cookie: %#v`, cookies)
|
||||||
|
if cmap[`MUSIC_U`] != `` {
|
||||||
|
env.Config.Custom.Wy_Refresh_Interval = now + 2147483647 - 86000
|
||||||
|
} else {
|
||||||
|
env.Config.Custom.Wy_Refresh_Interval = now + 86000
|
||||||
|
loger.Warn(`未发现有效结果,将在下次检测时再次尝试`)
|
||||||
|
}
|
||||||
|
err = env.Cfg.Save(``)
|
||||||
|
if err == nil {
|
||||||
|
loger.Info(`配置更新成功`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}, 86000, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
27
src/sources/custom/wy/login_refresh.go
Normal file
27
src/sources/custom/wy/login_refresh.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 登录刷新
|
||||||
|
func LoginRefresh(query ReqQuery) (*ReqAnswer, error) {
|
||||||
|
res, err := createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://music.163.com/weapi/login/token/refresh`,
|
||||||
|
map[string]any{},
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `weapi`,
|
||||||
|
UA: `pc`,
|
||||||
|
Cookie: query.Cookie,
|
||||||
|
RealIP: query.RealIP,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if code, ok := res.Body[`code`].(int); ok && err == nil {
|
||||||
|
if code == 200 {
|
||||||
|
res.Body[`cookie`] = strings.Join(res.Cookie, `;`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
@ -1,34 +1,76 @@
|
|||||||
package wy
|
package wy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
"lx-source/src/sources"
|
"lx-source/src/sources"
|
||||||
"lx-source/src/sources/builtin"
|
|
||||||
"lx-source/src/sources/custom/utils"
|
"lx-source/src/sources/custom/utils"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
"github.com/ZxwyWebSite/ztool/x/cookie"
|
"github.com/ZxwyWebSite/ztool/x/cookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type playInfo struct {
|
||||||
|
Data []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Br int `json:"br"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Md5 string `json:"md5"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
Expi int `json:"expi"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Gain float64 `json:"gain"`
|
||||||
|
Peak float64 `json:"peak"`
|
||||||
|
Fee int `json:"fee"`
|
||||||
|
Uf interface{} `json:"uf"`
|
||||||
|
Payed int `json:"payed"`
|
||||||
|
Flag int `json:"flag"`
|
||||||
|
CanExtend bool `json:"canExtend"`
|
||||||
|
FreeTrialInfo struct {
|
||||||
|
AlgData interface{} `json:"algData"`
|
||||||
|
End int `json:"end"`
|
||||||
|
FragmentType int `json:"fragmentType"`
|
||||||
|
Start int `json:"start"`
|
||||||
|
} `json:"freeTrialInfo"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
EncodeType string `json:"encodeType"`
|
||||||
|
FreeTrialPrivilege struct {
|
||||||
|
ResConsumable bool `json:"resConsumable"`
|
||||||
|
UserConsumable bool `json:"userConsumable"`
|
||||||
|
ListenType int `json:"listenType"`
|
||||||
|
CannotListenReason int `json:"cannotListenReason"`
|
||||||
|
PlayReason interface{} `json:"playReason"`
|
||||||
|
} `json:"freeTrialPrivilege"`
|
||||||
|
FreeTimeTrialPrivilege struct {
|
||||||
|
ResConsumable bool `json:"resConsumable"`
|
||||||
|
UserConsumable bool `json:"userConsumable"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
RemainTime int `json:"remainTime"`
|
||||||
|
} `json:"freeTimeTrialPrivilege"`
|
||||||
|
URLSource int `json:"urlSource"`
|
||||||
|
RightSource int `json:"rightSource"`
|
||||||
|
PodcastCtrp interface{} `json:"podcastCtrp"`
|
||||||
|
EffectTypes interface{} `json:"effectTypes"`
|
||||||
|
Time int `json:"time"`
|
||||||
|
} `json:"data"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
func Url(songMid, quality string) (ourl, msg string) {
|
func Url(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Wy`)
|
loger := env.Loger.NewGroup(`Wy`)
|
||||||
rquality, ok := brMap[quality]
|
rquality, ok := qualityMap[quality]
|
||||||
if !ok {
|
if !ok {
|
||||||
msg = sources.E_QNotSupport
|
msg = sources.E_QNotSupport
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cookies := cookie.Parse(env.Config.Custom.Wy_Cookie)
|
cookies := cookie.Parse(env.Config.Custom.Wy_Cookie)
|
||||||
answer, err := SongUrl(song_url_query{
|
answer, err := SongUrlV1(ReqQuery{
|
||||||
Cookie: cookie.ToMap(cookies),
|
Cookie: cookie.ToMap(cookies),
|
||||||
Ids: songMid,
|
Ids: songMid,
|
||||||
Br: rquality,
|
// Br: rquality,
|
||||||
|
Level: rquality,
|
||||||
})
|
})
|
||||||
var body builtin.WyApi_Song
|
var body playInfo
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = ztool.Val_MapToStruct(answer.Body, &body)
|
err = ztool.Val_MapToStruct(answer.Body, &body)
|
||||||
}
|
}
|
||||||
@ -43,65 +85,69 @@ func Url(songMid, quality string) (ourl, msg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data := body.Data[0]
|
data := body.Data[0]
|
||||||
br := strconv.Itoa(data.Br) // 注:由于flac返回br值不固定,暂无法进行比较
|
if data.Level != rquality {
|
||||||
if br != rquality && !ztool.Chk_IsMatch(br, sources.Q_flac, sources.Q_fl24) {
|
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, data.Level)
|
||||||
msg = sources.E_QNotMatch
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// br := strconv.Itoa(data.Br) // 注:由于flac返回br值不固定,暂无法进行比较
|
||||||
|
// if br != rquality && !ztool.Chk_IsMatch(br, sources.Q_flac, sources.Q_fl24) {
|
||||||
|
// msg = sources.E_QNotMatch
|
||||||
|
// return
|
||||||
|
// }
|
||||||
ourl = utils.DelQuery(data.URL)
|
ourl = utils.DelQuery(data.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func PyUrl(songMid, quality string) (ourl, msg string) {
|
// func PyUrl(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Wy`)
|
// loger := env.Loger.NewGroup(`Wy`)
|
||||||
rquality, ok := qualityMap[quality]
|
// rquality, ok := qualityMap[quality]
|
||||||
if !ok {
|
// if !ok {
|
||||||
msg = sources.E_QNotSupport
|
// msg = sources.E_QNotSupport
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
path := `/api/song/enhance/player/url/v1`
|
// path := `/api/song/enhance/player/url/v1`
|
||||||
requestUrl := `https://interface.music.163.com/eapi/song/enhance/player/url/v1`
|
// requestUrl := `https://interface.music.163.com/eapi/song/enhance/player/url/v1`
|
||||||
var body builtin.WyApi_Song
|
// var body builtin.WyApi_Song
|
||||||
text := ztool.Str_FastConcat(
|
// text := ztool.Str_FastConcat(
|
||||||
`{"encodeType":"flac","ids":["`, songMid, `"],"level":"`, rquality, `"}`,
|
// `{"encodeType":"flac","ids":["`, songMid, `"],"level":"`, rquality, `"}`,
|
||||||
)
|
// )
|
||||||
var form url.Values = eapiEncrypt(path, text)
|
// var form url.Values = eapiEncrypt(path, text)
|
||||||
// form, err := json.Marshal(eapiEncrypt(path, text))
|
// // form, err := json.Marshal(eapiEncrypt(path, text))
|
||||||
// if err == nil {
|
// // if err == nil {
|
||||||
err := ztool.Net_Request(
|
// err := ztool.Net_Request(
|
||||||
http.MethodPost, requestUrl,
|
// http.MethodPost, requestUrl,
|
||||||
strings.NewReader(form.Encode()), //bytes.NewReader(form),
|
// strings.NewReader(form.Encode()), //bytes.NewReader(form),
|
||||||
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{
|
// []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{
|
||||||
`Cookie`: env.Config.Custom.Wy_Cookie,
|
// `Cookie`: env.Config.Custom.Wy_Cookie,
|
||||||
})},
|
// })},
|
||||||
[]ztool.Net_ResHandlerFunc{
|
// []ztool.Net_ResHandlerFunc{
|
||||||
func(res *http.Response) error {
|
// func(res *http.Response) error {
|
||||||
body, err := io.ReadAll(res.Body)
|
// body, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
loger.Info(`%s`, body)
|
// loger.Info(`%s`, body)
|
||||||
return ztool.Err_EsContinue
|
// return ztool.Err_EsContinue
|
||||||
},
|
// },
|
||||||
ztool.Net_ResToStruct(&body),
|
// ztool.Net_ResToStruct(&body),
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
// }
|
// // }
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
loger.Error(`Request: %s`, err)
|
// loger.Error(`Request: %s`, err)
|
||||||
msg = sources.ErrHttpReq
|
// msg = sources.ErrHttpReq
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
loger.Debug(`Resp: %+v`, body)
|
// loger.Debug(`Resp: %+v`, body)
|
||||||
if len(body.Data) == 0 {
|
// if len(body.Data) == 0 {
|
||||||
msg = `No Data:无返回数据`
|
// msg = `No Data:无返回数据`
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
data := body.Data[0]
|
// data := body.Data[0]
|
||||||
if data.Level != rquality {
|
// if data.Level != rquality {
|
||||||
msg = sources.E_QNotMatch
|
// msg = sources.E_QNotMatch
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
ourl = utils.DelQuery(data.URL)
|
// ourl = utils.DelQuery(data.URL)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
@ -2,19 +2,25 @@ package wy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type song_url_query struct {
|
// type Query_song_url struct {
|
||||||
Cookie map[string]string
|
// Cookie map[string]string
|
||||||
Ids string
|
// Ids string
|
||||||
Br string
|
// Br string
|
||||||
RealIP string
|
// RealIP string
|
||||||
}
|
// }
|
||||||
|
|
||||||
func SongUrl(query song_url_query) (*reqAnswer, error) {
|
// 歌曲链接
|
||||||
|
func SongUrl(query ReqQuery) (*ReqAnswer, error) {
|
||||||
|
if query.Cookie == nil {
|
||||||
|
query.Cookie = make(map[string]string)
|
||||||
|
}
|
||||||
query.Cookie[`os`] = `pc`
|
query.Cookie[`os`] = `pc`
|
||||||
if query.Br == `` {
|
if query.Br == `` {
|
||||||
query.Br = `999000`
|
query.Br = `999000`
|
||||||
@ -28,7 +34,7 @@ func SongUrl(query song_url_query) (*reqAnswer, error) {
|
|||||||
`ids`: ztool.Str_FastConcat(`["`, strings.Join(ids, `","`), `"]`), //bytesconv.BytesToString(idj), //`["1998644237"]`,
|
`ids`: ztool.Str_FastConcat(`["`, strings.Join(ids, `","`), `"]`), //bytesconv.BytesToString(idj), //`["1998644237"]`,
|
||||||
`br`: query.Br, //ztool.Str_Select(query.Br, `999000`),
|
`br`: query.Br, //ztool.Str_Select(query.Br, `999000`),
|
||||||
}
|
}
|
||||||
return createRequest(
|
res, err := createRequest(
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
`https://interface3.music.163.com/eapi/song/enhance/player/url`,
|
`https://interface3.music.163.com/eapi/song/enhance/player/url`,
|
||||||
data,
|
data,
|
||||||
@ -39,4 +45,29 @@ func SongUrl(query song_url_query) (*reqAnswer, error) {
|
|||||||
Url: `/api/song/enhance/player/url`,
|
Url: `/api/song/enhance/player/url`,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
// 根据id排序
|
||||||
|
if length := len(ids); length > 1 && err == nil {
|
||||||
|
indexOf := make(map[string]int, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
indexOf[ids[i]] = i
|
||||||
|
}
|
||||||
|
if data, ok := res.Body[`data`].([]interface{}); ok {
|
||||||
|
sort.SliceStable(data, func(a, b int) bool {
|
||||||
|
da, oa := data[a].(map[string]interface{})
|
||||||
|
db, ob := data[b].(map[string]interface{})
|
||||||
|
if oa && ob {
|
||||||
|
ia, ka := da[`id`].(float64)
|
||||||
|
ib, kb := db[`id`].(float64)
|
||||||
|
if ka && kb {
|
||||||
|
ta := strconv.FormatInt(int64(ia), 10)
|
||||||
|
tb := strconv.FormatInt(int64(ib), 10)
|
||||||
|
return indexOf[ta] < indexOf[tb]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
res.Body[`data`] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
37
src/sources/custom/wy/song_url_v1.go
Normal file
37
src/sources/custom/wy/song_url_v1.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 歌曲链接 - v1
|
||||||
|
// 此版本不再采用 br 作为音质区分的标准
|
||||||
|
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||||
|
func SongUrlV1(query ReqQuery) (*ReqAnswer, error) {
|
||||||
|
if query.Cookie == nil {
|
||||||
|
query.Cookie = make(map[string]string)
|
||||||
|
}
|
||||||
|
query.Cookie[`os`] = `android`
|
||||||
|
query.Cookie[`appver`] = `8.10.05`
|
||||||
|
data := map[string]any{
|
||||||
|
`ids`: ztool.Str_FastConcat(`[`, query.Ids, `]`),
|
||||||
|
`level`: query.Level,
|
||||||
|
`encodeType`: `flac`,
|
||||||
|
}
|
||||||
|
if query.Level == `sky` /*|| query.Level == `jysky`*/ {
|
||||||
|
data[`immerseType`] = `c51`
|
||||||
|
}
|
||||||
|
return createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://interface.music.163.com/eapi/song/enhance/player/url/v1`,
|
||||||
|
data,
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `eapi`,
|
||||||
|
Cookie: query.Cookie,
|
||||||
|
RealIP: query.RealIP,
|
||||||
|
Url: `/api/song/enhance/player/url/v1`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -3,12 +3,12 @@ package wy
|
|||||||
import "lx-source/src/sources"
|
import "lx-source/src/sources"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
brMap = map[string]string{
|
// brMap = map[string]string{
|
||||||
sources.Q_128k: `128000`,
|
// sources.Q_128k: `128000`,
|
||||||
sources.Q_320k: `320000`,
|
// sources.Q_320k: `320000`,
|
||||||
sources.Q_flac: `1000000`, //`743625`,`915752`
|
// sources.Q_flac: `1000000`, //`743625`,`915752`
|
||||||
sources.Q_fl24: `2000000`, //`1453955`,`1683323`
|
// sources.Q_fl24: `2000000`, //`1453955`,`1683323`
|
||||||
}
|
// }
|
||||||
qualityMap = map[string]string{
|
qualityMap = map[string]string{
|
||||||
sources.Q_128k: `standard`,
|
sources.Q_128k: `standard`,
|
||||||
sources.Q_320k: `exhigh`,
|
sources.Q_320k: `exhigh`,
|
||||||
|
@ -28,24 +28,30 @@ var userAgentMap = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// reqCookie struct{}
|
ReqQuery struct {
|
||||||
|
Cookie map[string]string
|
||||||
|
RealIP string
|
||||||
|
Ids string
|
||||||
|
Br string
|
||||||
|
Level string
|
||||||
|
}
|
||||||
reqOptions struct {
|
reqOptions struct {
|
||||||
UA string
|
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
|
UA string
|
||||||
RealIP string
|
RealIP string
|
||||||
IP string
|
IP string
|
||||||
Cookie interface{}
|
|
||||||
Crypto string
|
Crypto string
|
||||||
Url string
|
Url string
|
||||||
|
Cookie interface{}
|
||||||
}
|
}
|
||||||
reqAnswer struct {
|
ReqAnswer struct {
|
||||||
Status int
|
Status int
|
||||||
Body map[string]any
|
Body map[string]any
|
||||||
Cookie []string
|
Cookie []string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func createRequest(method, url string, data map[string]any, options reqOptions) (*reqAnswer, error) {
|
func createRequest(method, url string, data map[string]any, options reqOptions) (*ReqAnswer, error) {
|
||||||
if options.Headers == nil {
|
if options.Headers == nil {
|
||||||
options.Headers = make(map[string]string)
|
options.Headers = make(map[string]string)
|
||||||
}
|
}
|
||||||
@ -96,26 +102,25 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
var form stdurl.Values
|
var form stdurl.Values
|
||||||
switch options.Crypto {
|
switch options.Crypto {
|
||||||
case `weapi`:
|
case `weapi`:
|
||||||
panic(`not support`)
|
options.Headers[`User-Agent`] = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69`
|
||||||
// options.Headers[`User-Agent`] = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69`
|
reg := regexp.MustCompile(`_csrf=([^(;|$)]+)`)
|
||||||
// reg := regexp.MustCompile(`_csrf=([^(;|$)]+)`)
|
csrfToken := reg.FindStringSubmatch(options.Headers[`Cookie`])
|
||||||
// csrfToken := reg.FindStringSubmatch(options.Headers[`Cookie`])
|
if len(csrfToken) > 1 {
|
||||||
// if len(csrfToken) > 1 {
|
data[`csrf_token`] = csrfToken[1]
|
||||||
// data[`csrf_token`] = csrfToken[1]
|
} else {
|
||||||
// } else {
|
data[`csrf_token`] = ``
|
||||||
// data[`csrf_token`] = ``
|
}
|
||||||
// }
|
form = weapi(data)
|
||||||
// data = weapi(data)
|
// fmt.Println(form.Encode())
|
||||||
// url = wapiReg.ReplaceAllString(url, `weapi`)
|
url = wapiReg.ReplaceAllString(url, `weapi`)
|
||||||
case `linuxapi`:
|
case `linuxapi`:
|
||||||
panic(`not support`)
|
form = linuxapi(map[string]any{
|
||||||
// data = linuxapi(
|
`method`: method,
|
||||||
// method,
|
`url`: wapiReg.ReplaceAllString(url, `weapi`),
|
||||||
// wapiReg.ReplaceAllString(url, `weapi`),
|
`params`: data,
|
||||||
// data,
|
})
|
||||||
// )
|
options.Headers[`User-Agent`] = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36`
|
||||||
// options.Headers[`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`
|
||||||
// url = `https://music.163.com/api/linux/forward`
|
|
||||||
case `eapi`:
|
case `eapi`:
|
||||||
cookie, ok := options.Cookie.(map[string]string)
|
cookie, ok := options.Cookie.(map[string]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -168,7 +173,7 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
// for k, v := range data {
|
// for k, v := range data {
|
||||||
// values.Add(k, v)
|
// values.Add(k, v)
|
||||||
// }
|
// }
|
||||||
answer := reqAnswer{Status: 500, Body: map[string]any{} /*, Cookie: []string{}*/}
|
answer := ReqAnswer{Status: 500, Body: map[string]any{} /*, Cookie: []string{}*/}
|
||||||
err := ztool.Net_Request(method, url,
|
err := ztool.Net_Request(method, url,
|
||||||
strings.NewReader(form.Encode()),
|
strings.NewReader(form.Encode()),
|
||||||
[]ztool.Net_ReqHandlerFunc{
|
[]ztool.Net_ReqHandlerFunc{
|
||||||
@ -178,17 +183,21 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
func(res *http.Response) error {
|
func(res *http.Response) error {
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
answer.Cookie = res.Header[`Set-Cookie`]
|
// fmt.Println(`body:`, string(body), "\nstr:", body)
|
||||||
reg := regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
|
if len(body) == 0 {
|
||||||
for i, v := range answer.Cookie {
|
err = errors.New(`nil Body`)
|
||||||
answer.Cookie[i] = reg.ReplaceAllString(v, ``)
|
|
||||||
}
|
|
||||||
if options.Crypto == `eapi` && body[0] != '{' {
|
|
||||||
err = json.Unmarshal(decrypt(body), &answer.Body)
|
|
||||||
} else {
|
|
||||||
err = json.Unmarshal(body, &answer.Body)
|
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
answer.Cookie = res.Header[`Set-Cookie`] //res.Header.Values(`set-cookie`)
|
||||||
|
reg := regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
|
||||||
|
for i, v := range answer.Cookie {
|
||||||
|
answer.Cookie[i] = reg.ReplaceAllString(v, ``)
|
||||||
|
}
|
||||||
|
if options.Crypto == `eapi` && body[0] != '{' {
|
||||||
|
err = json.Unmarshal(decrypt(body), &answer.Body)
|
||||||
|
} else {
|
||||||
|
err = json.Unmarshal(body, &answer.Body)
|
||||||
|
}
|
||||||
if code, ok := answer.Body[`code`].(string); ok {
|
if code, ok := answer.Body[`code`].(string); ok {
|
||||||
answer.Body[`code`], err = strconv.Atoi(code)
|
answer.Body[`code`], err = strconv.Atoi(code)
|
||||||
} else {
|
} else {
|
||||||
@ -212,7 +221,7 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
answer.Status = 502
|
answer.Status = 502
|
||||||
answer.Body = map[string]any{`code`: 502, `msg`: err}
|
answer.Body = map[string]any{`code`: 502, `msg`: err.Error()}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
27
update.md
27
update.md
@ -1,5 +1,32 @@
|
|||||||
## Lx-Source/更新日志
|
## Lx-Source/更新日志
|
||||||
|
|
||||||
|
#### \# 2024-01-21 v1.0.2-b12-d2 (dev)
|
||||||
|
+ 新版api结构设计(暂定)
|
||||||
|
```
|
||||||
|
# 基础接口
|
||||||
|
/
|
||||||
|
/link/{source}/{musicid}/{quality}
|
||||||
|
# 功能接口
|
||||||
|
/api/{source}/{method}/{query}
|
||||||
|
| 源 | 功能 | 参数 |
|
||||||
|
/api/wy/link/?id=xxx&quality=320k&key=xxx
|
||||||
|
# 软件接口
|
||||||
|
/app/{name}/{method}/{?query}
|
||||||
|
| 名称 | 功能 |
|
||||||
|
/app/lxmusic/link (参数通过post传入)
|
||||||
|
/app/musicfree/xxx (计划支持MusicFree)
|
||||||
|
```
|
||||||
|
+ 添加wy外链获取v1支持,并改用此版本
|
||||||
|
+ 计划:统一错误输出,进行以下分类
|
||||||
|
+ 验证失败(Verify Failed)、实际音质不匹配、无返回数据(No Data)、...
|
||||||
|
<!-- + 其它:这一段时间主要完善wy源接口,kg和mg账号源推迟更新 -->
|
||||||
|
|
||||||
|
#### \# 2024-01-19 v1.0.2-b12-d2 (dev)
|
||||||
|
+ 添加wy批量SongUrl获取排序功能
|
||||||
|
+ 完善wy请求加密支持
|
||||||
|
+ 添加wy刷新登录模块(beta)
|
||||||
|
+ 待优化:cookie需要频繁在map和string之间转换
|
||||||
|
|
||||||
#### \# 2024-01-18 v1.0.2-b12-d1 (dev)
|
#### \# 2024-01-18 v1.0.2-b12-d1 (dev)
|
||||||
+ 对部分功能实现方式进行优化,去除qualityMapReverse依赖
|
+ 对部分功能实现方式进行优化,去除qualityMapReverse依赖
|
||||||
+ 由于wy修改api验证方式,python版逻辑已不可用,现参考NeteaseCloudMusicApi项目进行修改
|
+ 由于wy修改api验证方式,python版逻辑已不可用,现参考NeteaseCloudMusicApi项目进行修改
|
||||||
|
Loading…
x
Reference in New Issue
Block a user