mirror of
https://github.com/ZxwyWebSite/lx-source.git
synced 2025-07-03 10:42:18 +08:00
328 lines
9.2 KiB
Go
328 lines
9.2 KiB
Go
package kw
|
||
|
||
import (
|
||
"errors"
|
||
"io"
|
||
"lx-source/src/env"
|
||
"lx-source/src/sources"
|
||
"lx-source/src/sources/custom/utils"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
|
||
"github.com/ZxwyWebSite/ztool"
|
||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||
"github.com/ZxwyWebSite/ztool/zcypt"
|
||
)
|
||
|
||
var (
|
||
kw_pool *sync.Pool
|
||
|
||
Url func(string, string) (string, string)
|
||
|
||
parsemod bool
|
||
convtype string
|
||
desource string
|
||
// desParse func([]byte, *playInfo) error
|
||
// desParse func(any) ztool.Net_ResHandlerFunc
|
||
)
|
||
|
||
func init() {
|
||
env.Inits.Add(func() {
|
||
loger := env.Loger.NewGroup(`KwInit`)
|
||
switch env.Config.Custom.Kw_Mode {
|
||
case `0`, `bdapi`:
|
||
loger.Debug(`use bdapi`)
|
||
if ztool.Chk_IsNilStr(
|
||
env.Config.Custom.Kw_Bd_Uid,
|
||
env.Config.Custom.Kw_Bd_Token,
|
||
env.Config.Custom.Kw_Bd_DevId,
|
||
) {
|
||
loger.Fatal(`使用bdapi且验证参数为空`)
|
||
}
|
||
// bdheader[`token`] = env.Config.Custom.Kw_Bd_Token
|
||
bdheader[`uid`] = env.Config.Custom.Kw_Bd_Uid
|
||
bdheader[`devId`] = env.Config.Custom.Kw_Bd_DevId
|
||
kw_pool = &sync.Pool{New: func() any { return new(kwApi_Song) }}
|
||
Url = bdapi
|
||
case `1`, `kwdes`:
|
||
Url = kwdes
|
||
switch env.Config.Custom.Kw_Des_Type {
|
||
case `0`, `text`:
|
||
loger.Debug(`use kwdes text`)
|
||
convtype = `convert_url2`
|
||
// desParse = txtParse
|
||
case `1`, `json`:
|
||
loger.Debug(`use kwdes json`)
|
||
convtype = `convert_url_with_sign`
|
||
// desParse = ztool.Net_ResToStruct
|
||
parsemod = true
|
||
case `2`, `anti`:
|
||
loger.Debug(`use kwdes anti`)
|
||
Url = manti
|
||
default:
|
||
loger.Fatal(`未定义的返回格式,请检查配置 [Custom].Kw_Des_Type`)
|
||
}
|
||
desheader[`User-Agent`] = env.Config.Custom.Kw_Des_Header
|
||
kw_pool = &sync.Pool{New: func() any { return new(playInfo) }}
|
||
if env.Config.Custom.Kw_Des_Source != `` {
|
||
desource = env.Config.Custom.Kw_Des_Source
|
||
} else {
|
||
dec, _ := zcypt.HexDecode([]byte{0x36, 0x62, 0x37, 0x37, 0x37, 0x30, 0x36, 0x63, 0x36, 0x31, 0x37, 0x39, 0x36, 0x35, 0x37, 0x32, 0x36, 0x38, 0x36, 0x34, 0x35, 0x66, 0x36, 0x31, 0x37, 0x32, 0x35, 0x66, 0x33, 0x35, 0x32, 0x65, 0x33, 0x31, 0x32, 0x65, 0x33, 0x30, 0x32, 0x65, 0x33, 0x30, 0x35, 0x66, 0x34, 0x32, 0x35, 0x66, 0x36, 0x61, 0x36, 0x39, 0x36, 0x31, 0x36, 0x62, 0x36, 0x66, 0x36, 0x65, 0x36, 0x37, 0x35, 0x66, 0x37, 0x36, 0x36, 0x38, 0x32, 0x65, 0x36, 0x31, 0x37, 0x30, 0x36, 0x62})
|
||
desource = bytesconv.BytesToString(dec)
|
||
}
|
||
default:
|
||
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Kw_Mode`)
|
||
}
|
||
loger.Free()
|
||
})
|
||
}
|
||
|
||
func bdapi(songMid, quality string) (ourl, msg string) {
|
||
loger := env.Loger.NewGroup(`Kw`)
|
||
defer loger.Free()
|
||
info, ok := fileInfo[quality]
|
||
if !ok {
|
||
msg = sources.E_QNotSupport
|
||
return
|
||
}
|
||
resp := kw_pool.Get().(*kwApi_Song)
|
||
defer kw_pool.Put(resp)
|
||
|
||
url := ztool.Str_FastConcat(
|
||
`https://bd-api.kuwo.cn/api/service/music/downloadInfo/`, songMid,
|
||
`?isMv=0&format=`, info.E,
|
||
`&br=`, info.H, info.E, //`&level=`,
|
||
`&uid=`, env.Config.Custom.Kw_Bd_Uid,
|
||
`&token=`, env.Config.Custom.Kw_Bd_Token,
|
||
)
|
||
// jx.Debug(`Kw, Url: %s`, url)
|
||
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, bdheader, &resp)
|
||
if err != nil {
|
||
loger.Error(`HttpReq: %s`, err)
|
||
msg = sources.ErrHttpReq
|
||
return
|
||
}
|
||
loger.Debug(`Resp: %+v`, resp)
|
||
if resp.Code != 200 || resp.Data.AudioInfo.Bitrate == `1` {
|
||
// jx.Debug(`Kw, Err: %#v`, resp)
|
||
msg = ztool.Str_FastConcat(strconv.Itoa(resp.Code), `: `, resp.Msg)
|
||
return
|
||
}
|
||
ourl = utils.DelQuery(resp.Data.URL) //strings.Split(resp.Data.URL, `?`)[0]
|
||
return
|
||
}
|
||
|
||
func kwdes(songMid, quality string) (ourl, msg string) {
|
||
loger := env.Loger.NewGroup(`Kw`)
|
||
defer loger.Free()
|
||
infoFile, ok := fileInfo[quality]
|
||
if !ok {
|
||
msg = sources.E_QNotSupport
|
||
return
|
||
}
|
||
target_url := ztool.Str_FastConcat(
|
||
`https://mobi.kuwo.cn/mobi.s?f=kuwo&q=`,
|
||
base64_encrypt(ztool.Str_FastConcat(
|
||
`corp=kuwo&p2p=1&sig=0¬race=0&priority=bitrate&network=WIFI&mode=down`,
|
||
`&source=`, desource,
|
||
`&type=`, convtype,
|
||
`&br=`, infoFile.H, infoFile.E,
|
||
`&format=`, infoFile.E,
|
||
`&rid=`, songMid,
|
||
)),
|
||
)
|
||
if parsemod {
|
||
resp := kw_pool.Get().(*playInfo)
|
||
defer kw_pool.Put(resp)
|
||
|
||
err := ztool.Net_Request(http.MethodGet, target_url, nil,
|
||
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(desheader)},
|
||
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
|
||
)
|
||
if err != nil {
|
||
loger.Error(`Request: %s`, err)
|
||
msg = sources.ErrHttpReq
|
||
return
|
||
}
|
||
resp.Data.URL = utils.DelQuery(resp.Data.URL)
|
||
loger.Debug(`Resp: %+v`, resp)
|
||
if resp.Code != http.StatusOK {
|
||
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
|
||
loger.Debug(msg)
|
||
return
|
||
}
|
||
realQuality := strconv.Itoa(resp.Data.Bitrate)
|
||
if realQuality != infoFile.H[:len(infoFile.H)-1] /*&& resp.Data.Bitrate != 1*/ {
|
||
msg = sources.E_QNotMatch
|
||
if !env.Config.Source.ForceFallback {
|
||
return
|
||
}
|
||
}
|
||
ourl = resp.Data.URL //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
|
||
return
|
||
}
|
||
ztool.Net_Request(http.MethodGet, target_url, nil,
|
||
[]ztool.Net_ReqHandlerFunc{
|
||
ztool.Net_ReqAddHeader(desheader),
|
||
},
|
||
[]ztool.Net_ResHandlerFunc{
|
||
func(res *http.Response) (err error) {
|
||
data, err := io.ReadAll(res.Body)
|
||
if err != nil {
|
||
msg = err.Error()
|
||
return
|
||
}
|
||
if res.StatusCode != http.StatusOK {
|
||
msg = ztool.Str_FastConcat(`failed: `, res.Status)
|
||
loger.Debug(msg)
|
||
return
|
||
}
|
||
infoData := mkMap(data)
|
||
loger.Debug(`infoData: %+v`, infoData)
|
||
realQuality := infoData[`bitrate`]
|
||
if realQuality != infoFile.H[:len(infoFile.H)-1] {
|
||
msg = sources.E_QNotMatch
|
||
if !env.Config.Source.ForceFallback {
|
||
return
|
||
}
|
||
}
|
||
ourl = utils.DelQuery(infoData[`url`]) //infoData[`url`][:strings.Index(infoData[`url`], `?`)]
|
||
return
|
||
},
|
||
},
|
||
)
|
||
return
|
||
}
|
||
|
||
// 一种替代方案,仅在试听可用时获取cdn前缀,缺点是无法获取不能试听的歌曲
|
||
func manti(songMid, quality string) (ourl, msg string) {
|
||
loger := env.Loger.NewGroup(`Kw`)
|
||
defer loger.Free()
|
||
infoFile, ok := fileInfo[quality]
|
||
if !ok {
|
||
msg = sources.E_QNotSupport
|
||
return
|
||
}
|
||
// 获取CDN地址
|
||
// var out_a string
|
||
/*var out_c struct {
|
||
// Timestamp int `json:"timestamp"`
|
||
Songs []struct {
|
||
ID int `json:"id"`
|
||
Duration int `json:"duration"`
|
||
URL string `json:"url"`
|
||
HTTPS string `json:"https"`
|
||
CarURL string `json:"car_url"`
|
||
CarURLHTTPS string `json:"car_url_https"`
|
||
Format string `json:"format"`
|
||
Br int `json:"br"`
|
||
OverseasCopyright string `json:"overseas_copyright"`
|
||
Start int `json:"start"`
|
||
End int `json:"end"`
|
||
Group string `json:"group"`
|
||
} `json:"songs"`
|
||
IP string `json:"ip"`
|
||
Country string `json:"country"`
|
||
Region string `json:"region"`
|
||
Locationid int `json:"locationid"`
|
||
Code int `json:"code"`
|
||
Result string `json:"result"`
|
||
}
|
||
err := ztool.Net_Request(
|
||
http.MethodGet,
|
||
`https://musicpay30.kuwo.cn/audi.tion?op=query&ids=`+songMid,
|
||
nil,
|
||
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
|
||
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out_c)},
|
||
)
|
||
if err != nil {
|
||
loger.Error(`Request: %s`, err)
|
||
msg = sources.ErrHttpReq
|
||
return
|
||
}*/
|
||
// 获取音频路径
|
||
if quality != sources.Q_128k {
|
||
ourl, msg = manti(songMid, sources.Q_128k)
|
||
if msg != `` {
|
||
return
|
||
}
|
||
if i := strings.LastIndexByte(ourl, '/'); i != -1 {
|
||
if ourl[i+1:] == `2272659253.mp3` {
|
||
msg = sources.ErrNoLink
|
||
return
|
||
}
|
||
}
|
||
target_url := ztool.Str_FastConcat(
|
||
`https://mobi.kuwo.cn/mobi.s?f=web&type=convert_url`,
|
||
`&br=`, infoFile.H, infoFile.E,
|
||
`&format=`, infoFile.E,
|
||
`&rid=`, songMid,
|
||
)
|
||
var out_u, realQuality string
|
||
err := ztool.Net_Request(
|
||
http.MethodGet, target_url, nil,
|
||
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
|
||
[]ztool.Net_ResHandlerFunc{func(res *http.Response) (err error) {
|
||
var data []byte
|
||
data, err = io.ReadAll(res.Body)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if res.StatusCode != http.StatusOK {
|
||
return errors.New(`failed: ` + res.Status)
|
||
}
|
||
infoData := mkMap(data)
|
||
loger.Debug(`uData: %+v`, infoData)
|
||
realQuality = infoData[`bitrate`]
|
||
out_u = utils.DelQuery(infoData[`url`])
|
||
return
|
||
}},
|
||
)
|
||
if err != nil {
|
||
loger.Error(`Request: %s`, err)
|
||
msg = sources.ErrHttpReq
|
||
return
|
||
}
|
||
if realQuality != infoFile.H[:len(infoFile.H)-1] {
|
||
msg = sources.E_QNotMatch
|
||
if !env.Config.Source.ForceFallback {
|
||
return
|
||
}
|
||
}
|
||
if ourl != `` && out_u != `` {
|
||
ourl = ourl[:24] + out_u
|
||
}
|
||
return
|
||
}
|
||
resp := kw_pool.Get().(*playInfo)
|
||
defer kw_pool.Put(resp)
|
||
|
||
err := ztool.Net_Request(
|
||
http.MethodGet, `https://mobi.kuwo.cn/mobi.s?f=web&type=convert_url_with_sign&br=128kmp3&format=mp3&rid=`+songMid, nil,
|
||
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(desheader)},
|
||
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
|
||
)
|
||
if err != nil {
|
||
loger.Error(`Request: %s`, err)
|
||
msg = sources.ErrHttpReq
|
||
return
|
||
}
|
||
loger.Debug(`tData: %+v`, resp)
|
||
if resp.Code != http.StatusOK {
|
||
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
|
||
loger.Debug(msg)
|
||
return
|
||
}
|
||
realQuality := strconv.Itoa(resp.Data.Bitrate)
|
||
if realQuality != infoFile.H[:len(infoFile.H)-1] && resp.Data.Bitrate != 1 {
|
||
msg = sources.E_QNotMatch
|
||
if !env.Config.Source.ForceFallback {
|
||
return
|
||
}
|
||
}
|
||
ourl = utils.DelQuery(resp.Data.URL)
|
||
return
|
||
}
|