From 0b7e1a9425fea81324d6e3e4650c916568eafb23 Mon Sep 17 00:00:00 2001 From: ZxwyWebSite Date: Mon, 1 Jan 2024 16:01:30 +0800 Subject: [PATCH] 2023-12-31 v1.0.2-b0.8.2 --- .gitignore | 2 +- readme.md | 4 +- src/env/env.go | 14 ++ src/middleware/auth/auth.go | 6 +- src/sources/builtin/driver.go | 67 ++++---- src/sources/builtin/types.go | 160 +++++++++---------- src/sources/custom/tx/QMWSign.go | 126 +++++++++++++++ src/sources/custom/tx/info.go | 25 +++ src/sources/custom/tx/musicinfo.go | 247 +++++++++++++++++++++++++++++ src/sources/custom/tx/player.go | 137 ++++++++++++++++ src/sources/custom/tx/utils.go | 65 ++++++++ src/sources/custom/utils/utils.go | 12 ++ src/sources/source.go | 13 ++ update.md | 6 + 14 files changed, 770 insertions(+), 114 deletions(-) create mode 100644 src/sources/custom/tx/QMWSign.go create mode 100644 src/sources/custom/tx/info.go create mode 100644 src/sources/custom/tx/musicinfo.go create mode 100644 src/sources/custom/tx/player.go create mode 100644 src/sources/custom/tx/utils.go create mode 100644 src/sources/custom/utils/utils.go diff --git a/.gitignore b/.gitignore index 0a7efd3..426d694 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ bin/ # cache/ data/ -outdated/ +.outdated/ # conf.ini test.go test_test.go diff --git a/readme.md b/readme.md index ed952a6..f1a1323 100644 --- a/readme.md +++ b/readme.md @@ -11,9 +11,9 @@ ## ZxwyWebSite/LX-Source ### 简介 + LX-Music 解析源 (洛雪音乐自定义源) -+ 使用Golang编写,运行效率较高 ++ **由于本项目的特殊性,请低调使用,切勿宣传** + 测试阶段,不代表最终品质 -+ 验证部分暂未完善,建议仅本地使用,不要公开发布 ++ 验证部分暂未完善,建议仅本地部署,不要公开发布 + 视频教程:[使用教程.mp4](https://r2eu.zxwy.link/gh/lx-source/v1.0.2-b0.1/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B.mp4) diff --git a/src/env/env.go b/src/env/env.go index ebc1f2b..506b4ce 100644 --- a/src/env/env.go +++ b/src/env/env.go @@ -63,6 +63,19 @@ type ( // 平台账号 // ...(待实现) } // `comment:""` + Conf_Custom struct { + // kg (暂未实现) + // Kg_Enable bool `comment:"是否开启小枸源"` + // tx + Tx_Enable bool `comment:"是否开启小秋源"` + Tx_Ukey string `comment:"Cookie中/客户端的请求体中的(comm.authst)"` + Tx_Uuin string `comment:"key对应的QQ号"` + // wy (暂未实现) + // Wy_Enable bool `comment:"是否开启小芸源"` + // Wy_Cookie string `comment:"账号cookie数据"` + // mg (暂未实现) + // Mg_Enable bool `comment:"是否开启小蜜源"` + } Conf_Script struct { Ver string `comment:"自定义脚本版本" json:"ver"` Log string `comment:"更新日志" json:"log"` @@ -87,6 +100,7 @@ type ( Apis Conf_Apis `comment:"接口设置"` Auth Conf_Auth `comment:"访问控制"` Source Conf_Source `comment:"解析源配置"` + Custom Conf_Custom `comment:"解析账号配置"` Script Conf_Script `comment:"自定义脚本更新"` // ini:",omitempty" Cache Conf_Cache `comment:"音乐缓存设置"` } diff --git a/src/middleware/auth/auth.go b/src/middleware/auth/auth.go index 62759e7..da9547f 100644 --- a/src/middleware/auth/auth.go +++ b/src/middleware/auth/auth.go @@ -46,6 +46,8 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) { loger.Debug(`RateLimit Enabled`) loger.Info(`已启用速率限制,当前配置 %v/%v`, env.Config.Auth.RateLimit_Single, env.Config.Auth.RateLimit_Block) newRateLimit := func() *RateLimit { return &RateLimit{Tim: time.Now().Unix(), Num: 1} } + block_int64 := int64(env.Config.Auth.RateLimit_Block) + block_int := int(env.Config.Auth.RateLimit_Block) out = append(out, func(c *gin.Context) { resp.Wrap(c, func() *resp.Resp { rip := c.RemoteIP() @@ -57,7 +59,7 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) { if ok { if oip, ok := cip.(*RateLimit); ok { loger.Debug(`GetMemOut: %+v`, oip) - if oip.Tim+int64(env.Config.Auth.RateLimit_Block) > time.Now().Unix() { + if oip.Tim+block_int64 > time.Now().Unix() { oi := atomic.AddUint32(&oip.Num, 1) if oi > env.Config.Auth.RateLimit_Single { return &resp.Resp{Code: 5, Msg: `请求过快,请稍后重试`} @@ -67,7 +69,7 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) { } } val := newRateLimit() - if err := env.Cache.Set(rip, val, int(env.Config.Auth.RateLimit_Block)); err != nil { + if err := env.Cache.Set(rip, val, block_int); err != nil { loger.Error(`写入内存: %s`, err) return &resp.Resp{Code: 4, Msg: `速率限制内部异常,请联系网站管理员`} } diff --git a/src/sources/builtin/driver.go b/src/sources/builtin/driver.go index 51a4a3a..b94e8db 100644 --- a/src/sources/builtin/driver.go +++ b/src/sources/builtin/driver.go @@ -5,6 +5,7 @@ import ( "lx-source/src/caches" "lx-source/src/env" "lx-source/src/sources" + "lx-source/src/sources/custom/tx" "net/http" "strconv" "strings" @@ -28,7 +29,7 @@ var ( mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }} kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }} kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }} - tx_pool = &sync.Pool{New: func() any { return new(res_tx) }} + // tx_pool = &sync.Pool{New: func() any { return new(res_tx) }} ) const ( @@ -161,37 +162,45 @@ func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) { } outlink = data.PlayBackupURL case s_tx: - resp := tx_pool.Get().(*res_tx) - defer tx_pool.Put(resp) - sep := c.Split() - url := ztool.Str_FastConcat(api_tx, - `{"comm":{"ct":24,"cv":0,"format":"json","uin":"10086"},"req":{"method":"GetCdnDispatch","module":"CDN.SrfCdnDispatchServer","param":{"calltype":0,"guid":"1535153710","userip":""}},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{`, - func(s string) string { - if s == `` { - return `` - } - return ztool.Str_FastConcat(`"filename":["`, rquery, s, `.`, c.Extname, `"],`) - }(sep[1]), - `"guid":"1535153710","loginflag":1,"platform":"20","songmid":["`, sep[0], `"],"songtype":[0],"uin":"10086"}}}`, - ) - // jx.Debug(`Tx, Url: %s`, url) - out, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_tx, &resp) - if err != nil { - jx.Error(`Tx, HttpReq: %s`, err) - msg = errHttpReq + ourl, emsg := tx.Url(sep[0], c.Quality) + if emsg != `` { + msg = emsg return } - jx.Debug(`Tx, Resp: %s`, out) - if resp.Code != 0 { - msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.Code)) - return - } - if resp.Req0.Data.Midurlinfo[0].Purl == `` { - msg = errNoLink - return - } - outlink = ztool.Str_FastConcat(`https://dl.stream.qqmusic.qq.com/`, resp.Req0.Data.Midurlinfo[0].Purl) + outlink = ourl + // case `otx`: + // resp := tx_pool.Get().(*res_tx) + // defer tx_pool.Put(resp) + + // sep := c.Split() + // url := ztool.Str_FastConcat(api_tx, + // `{"comm":{"ct":24,"cv":0,"format":"json","uin":"10086"},"req":{"method":"GetCdnDispatch","module":"CDN.SrfCdnDispatchServer","param":{"calltype":0,"guid":"1535153710","userip":""}},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{`, + // func(s string) string { + // if s == `` { + // return `` + // } + // return ztool.Str_FastConcat(`"filename":["`, rquery, s, `.`, c.Extname, `"],`) + // }(sep[1]), + // `"guid":"1535153710","loginflag":1,"platform":"20","songmid":["`, sep[0], `"],"songtype":[0],"uin":"10086"}}}`, + // ) + // // jx.Debug(`Tx, Url: %s`, url) + // out, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_tx, &resp) + // if err != nil { + // jx.Error(`Tx, HttpReq: %s`, err) + // msg = errHttpReq + // return + // } + // jx.Debug(`Tx, Resp: %s`, out) + // if resp.Code != 0 { + // msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.Code)) + // return + // } + // if resp.Req0.Data.Midurlinfo[0].Purl == `` { + // msg = errNoLink + // return + // } + // outlink = ztool.Str_FastConcat(`https://dl.stream.qqmusic.qq.com/`, resp.Req0.Data.Midurlinfo[0].Purl) default: msg = `不支持的平台` return diff --git a/src/sources/builtin/types.go b/src/sources/builtin/types.go index 802fbca..8de403a 100644 --- a/src/sources/builtin/types.go +++ b/src/sources/builtin/types.go @@ -124,84 +124,84 @@ type ( // EVideoID string `json:"e_video_id"` } // 腾讯试听接口 - res_tx struct { - Code int `json:"code"` - // Ts int64 `json:"ts"` - // StartTs int64 `json:"start_ts"` - // Traceid string `json:"traceid"` - // Req struct { - // Code int `json:"code"` - // Data struct { - // Expiration int `json:"expiration"` - // Freeflowsip []string `json:"freeflowsip"` - // Keepalivefile string `json:"keepalivefile"` - // Msg string `json:"msg"` - // Retcode int `json:"retcode"` - // Servercheck string `json:"servercheck"` - // Sip []string `json:"sip"` - // Testfile2G string `json:"testfile2g"` - // Testfilewifi string `json:"testfilewifi"` - // Uin string `json:"uin"` - // Userip string `json:"userip"` - // Vkey string `json:"vkey"` - // } `json:"data"` - // } `json:"req"` - Req0 struct { - Code int `json:"code"` - Data struct { - // Uin string `json:"uin"` - // Retcode int `json:"retcode"` - // VerifyType int `json:"verify_type"` - // LoginKey string `json:"login_key"` - // Msg string `json:"msg"` - // Sip []string `json:"sip"` - // Thirdip []string `json:"thirdip"` - // Testfile2G string `json:"testfile2g"` - // Testfilewifi string `json:"testfilewifi"` - Midurlinfo []struct { - // Songmid string `json:"songmid"` - // Filename string `json:"filename"` - Purl string `json:"purl"` - // Errtype string `json:"errtype"` - // P2Pfromtag int `json:"p2pfromtag"` - // Qmdlfromtag int `json:"qmdlfromtag"` - // CommonDownfromtag int `json:"common_downfromtag"` - // VipDownfromtag int `json:"vip_downfromtag"` - // Pdl int `json:"pdl"` - // Premain int `json:"premain"` - // Hisdown int `json:"hisdown"` - // Hisbuy int `json:"hisbuy"` - // UIAlert int `json:"uiAlert"` - // Isbuy int `json:"isbuy"` - // Pneedbuy int `json:"pneedbuy"` - // Pneed int `json:"pneed"` - // Isonly int `json:"isonly"` - // Onecan int `json:"onecan"` - // Result int `json:"result"` - // Tips string `json:"tips"` - // Opi48Kurl string `json:"opi48kurl"` - // Opi96Kurl string `json:"opi96kurl"` - // Opi192Kurl string `json:"opi192kurl"` - // Opiflackurl string `json:"opiflackurl"` - // Opi128Kurl string `json:"opi128kurl"` - // Opi192Koggurl string `json:"opi192koggurl"` - // Wififromtag string `json:"wififromtag"` - // Flowfromtag string `json:"flowfromtag"` - // Wifiurl string `json:"wifiurl"` - // Flowurl string `json:"flowurl"` - // Vkey string `json:"vkey"` - // Opi30Surl string `json:"opi30surl"` - // Ekey string `json:"ekey"` - // AuthSwitch int `json:"auth_switch"` - // Subcode int `json:"subcode"` - // Opi96Koggurl string `json:"opi96koggurl"` - // AuthSwitch2 int `json:"auth_switch2"` - } `json:"midurlinfo"` - // Servercheck string `json:"servercheck"` - // Expiration int `json:"expiration"` - } `json:"data"` - } `json:"req_0"` - } + // res_tx struct { + // Code int `json:"code"` + // // Ts int64 `json:"ts"` + // // StartTs int64 `json:"start_ts"` + // // Traceid string `json:"traceid"` + // // Req struct { + // // Code int `json:"code"` + // // Data struct { + // // Expiration int `json:"expiration"` + // // Freeflowsip []string `json:"freeflowsip"` + // // Keepalivefile string `json:"keepalivefile"` + // // Msg string `json:"msg"` + // // Retcode int `json:"retcode"` + // // Servercheck string `json:"servercheck"` + // // Sip []string `json:"sip"` + // // Testfile2G string `json:"testfile2g"` + // // Testfilewifi string `json:"testfilewifi"` + // // Uin string `json:"uin"` + // // Userip string `json:"userip"` + // // Vkey string `json:"vkey"` + // // } `json:"data"` + // // } `json:"req"` + // Req0 struct { + // Code int `json:"code"` + // Data struct { + // // Uin string `json:"uin"` + // // Retcode int `json:"retcode"` + // // VerifyType int `json:"verify_type"` + // // LoginKey string `json:"login_key"` + // // Msg string `json:"msg"` + // // Sip []string `json:"sip"` + // // Thirdip []string `json:"thirdip"` + // // Testfile2G string `json:"testfile2g"` + // // Testfilewifi string `json:"testfilewifi"` + // Midurlinfo []struct { + // // Songmid string `json:"songmid"` + // // Filename string `json:"filename"` + // Purl string `json:"purl"` + // // Errtype string `json:"errtype"` + // // P2Pfromtag int `json:"p2pfromtag"` + // // Qmdlfromtag int `json:"qmdlfromtag"` + // // CommonDownfromtag int `json:"common_downfromtag"` + // // VipDownfromtag int `json:"vip_downfromtag"` + // // Pdl int `json:"pdl"` + // // Premain int `json:"premain"` + // // Hisdown int `json:"hisdown"` + // // Hisbuy int `json:"hisbuy"` + // // UIAlert int `json:"uiAlert"` + // // Isbuy int `json:"isbuy"` + // // Pneedbuy int `json:"pneedbuy"` + // // Pneed int `json:"pneed"` + // // Isonly int `json:"isonly"` + // // Onecan int `json:"onecan"` + // // Result int `json:"result"` + // // Tips string `json:"tips"` + // // Opi48Kurl string `json:"opi48kurl"` + // // Opi96Kurl string `json:"opi96kurl"` + // // Opi192Kurl string `json:"opi192kurl"` + // // Opiflackurl string `json:"opiflackurl"` + // // Opi128Kurl string `json:"opi128kurl"` + // // Opi192Koggurl string `json:"opi192koggurl"` + // // Wififromtag string `json:"wififromtag"` + // // Flowfromtag string `json:"flowfromtag"` + // // Wifiurl string `json:"wifiurl"` + // // Flowurl string `json:"flowurl"` + // // Vkey string `json:"vkey"` + // // Opi30Surl string `json:"opi30surl"` + // // Ekey string `json:"ekey"` + // // AuthSwitch int `json:"auth_switch"` + // // Subcode int `json:"subcode"` + // // Opi96Koggurl string `json:"opi96koggurl"` + // // AuthSwitch2 int `json:"auth_switch2"` + // } `json:"midurlinfo"` + // // Servercheck string `json:"servercheck"` + // // Expiration int `json:"expiration"` + // } `json:"data"` + // } `json:"req_0"` + // } ) const ( @@ -255,12 +255,12 @@ var ( api_mg string api_kw string api_kg string = `https://wwwapi.kugou.com/yy/index.php?r=play/getdata&platid=4&mid=1` - api_tx string = `https://u.y.qq.com/cgi-bin/musicu.fcg?data=` + // api_tx string = `https://u.y.qq.com/cgi-bin/musicu.fcg?data=` // Headers header_wy map[string]string header_mg map[string]string header_kw map[string]string - header_tx = map[string]string{`Referer`: `https://y.qq.com/`} + // header_tx = map[string]string{`Referer`: `https://y.qq.com/`} ) func init() { diff --git a/src/sources/custom/tx/QMWSign.go b/src/sources/custom/tx/QMWSign.go new file mode 100644 index 0000000..633e993 --- /dev/null +++ b/src/sources/custom/tx/QMWSign.go @@ -0,0 +1,126 @@ +package tx + +import ( + "crypto/md5" + "encoding/hex" + "regexp" + "strings" + + "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/x/bytesconv" +) + +func v(b string) string { + res := []byte{} + p := [...]int{21, 4, 9, 26, 16, 20, 27, 30} + for _, x := range p { + res = append(res, b[x]) + } + return bytesconv.BytesToString(res) //string(res) +} + +func c(b string) string { + res := []byte{} + p := [...]int{18, 11, 3, 2, 1, 7, 6, 25} + for _, x := range p { + res = append(res, b[x]) + } + return bytesconv.BytesToString(res) //string(res) +} + +func y(a, b, c int) (e []int) { + // e := []int{} + r25 := a >> 2 + if b != 0 && c != 0 { + r26 := a & 3 + r26_2 := r26 << 4 + r26_3 := b >> 4 + r26_4 := r26_2 | r26_3 + r27 := b & 15 + r27_2 := r27 << 2 + r27_3 := r27_2 | (c >> 6) + r28 := c & 63 + e = append(e, r25) + e = append(e, r26_4) + e = append(e, r27_3) + e = append(e, r28) + } else { + r10 := a >> 2 + r11 := a & 3 + r11_2 := r11 << 4 + e = append(e, r10) + e = append(e, r11_2) + } + return //e +} + +func n(ls []int) string { + e := []int{} + for i, r := 0, len(ls); i < r; i += 3 { + if i < r-2 { + e = append(e, y(ls[i], ls[i+1], ls[i+2])...) + } else { + e = append(e, y(ls[i], 0, 0)...) + } + } + res := []byte{} + b64all := `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=` + for _, i := range e { + res = append(res, b64all[i]) + } + return bytesconv.BytesToString(res) +} + +func t(b string) (res []int) { + zd := map[string]int{ + `0`: 0, + `1`: 1, + `2`: 2, + `3`: 3, + `4`: 4, + `5`: 5, + `6`: 6, + `7`: 7, + `8`: 8, + `9`: 9, + `A`: 10, + `B`: 11, + `C`: 12, + `D`: 13, + `E`: 14, + `F`: 15, + } + ol := [...]int{212, 45, 80, 68, 195, 163, 163, 203, 157, 220, 254, 91, 204, 79, 104, 6} + // res := []int{} + j := 0 + for i, r := 0, len(b); i < r; i += 2 { + one := zd[string(b[i])] + two := zd[string(b[i+1])] + r := one*16 ^ two + // if j >= 16 { + // break + // } + res = append(res, r^ol[j]) + j++ + } + return //res +} + +func createMD5(s []byte) string { + hash := md5.New() + hash.Write(s) + return hex.EncodeToString(hash.Sum(nil)) +} + +func sign(params []byte) string { + md5Str := strings.ToUpper(createMD5(params)) + h := v(md5Str) + e := c(md5Str) + ls := t(md5Str) + m := n(ls) + res := ztool.Str_FastConcat(`zzb`, h, m, e) //`zzb` + h + m + e + res = strings.ToLower(res) + r := regexp.MustCompile(`[\/+]`) + res = r.ReplaceAllString(res, ``) + return res +} diff --git a/src/sources/custom/tx/info.go b/src/sources/custom/tx/info.go new file mode 100644 index 0000000..7b91d23 --- /dev/null +++ b/src/sources/custom/tx/info.go @@ -0,0 +1,25 @@ +package tx + +// func Info(songMid string) (info any, msg string) { +// req, emsg := getMusicInfo(songMid) +// if emsg != `` { +// msg = emsg +// return +// } +// var singerList []any +// for _, s := range req.TrackInfo.Singer { +// // item := new(struct{ +// // ID int `json:"id"` +// // Str string +// // }) +// // item.ID = s.ID +// singerList = append(singerList, s) +// } +// var file_info map[string]struct{ +// Size interface{} +// } +// if req.TrackInfo.File.Size128Mp3 != 0 { +// file_info[`128k`].Size = +// } +// return +// } diff --git a/src/sources/custom/tx/musicinfo.go b/src/sources/custom/tx/musicinfo.go new file mode 100644 index 0000000..35a7091 --- /dev/null +++ b/src/sources/custom/tx/musicinfo.go @@ -0,0 +1,247 @@ +package tx + +import ( + "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/x/bytesconv" +) + +type musicInfo struct { + // Info struct { + // Company struct { + // Title string `json:"title"` + // Type string `json:"type"` + // Content []struct { + // ID int `json:"id"` + // Value string `json:"value"` + // Mid string `json:"mid"` + // Type int `json:"type"` + // ShowType int `json:"show_type"` + // IsParent int `json:"is_parent"` + // Picurl string `json:"picurl"` + // ReadCnt int `json:"read_cnt"` + // Author string `json:"author"` + // Jumpurl string `json:"jumpurl"` + // OriPicurl string `json:"ori_picurl"` + // } `json:"content"` + // Pos int `json:"pos"` + // More int `json:"more"` + // Selected string `json:"selected"` + // UsePlatform int `json:"use_platform"` + // } `json:"company"` + // Genre struct { + // Title string `json:"title"` + // Type string `json:"type"` + // Content []struct { + // ID int `json:"id"` + // Value string `json:"value"` + // Mid string `json:"mid"` + // Type int `json:"type"` + // ShowType int `json:"show_type"` + // IsParent int `json:"is_parent"` + // Picurl string `json:"picurl"` + // ReadCnt int `json:"read_cnt"` + // Author string `json:"author"` + // Jumpurl string `json:"jumpurl"` + // OriPicurl string `json:"ori_picurl"` + // } `json:"content"` + // Pos int `json:"pos"` + // More int `json:"more"` + // Selected string `json:"selected"` + // UsePlatform int `json:"use_platform"` + // } `json:"genre"` + // Lan struct { + // Title string `json:"title"` + // Type string `json:"type"` + // Content []struct { + // ID int `json:"id"` + // Value string `json:"value"` + // Mid string `json:"mid"` + // Type int `json:"type"` + // ShowType int `json:"show_type"` + // IsParent int `json:"is_parent"` + // Picurl string `json:"picurl"` + // ReadCnt int `json:"read_cnt"` + // Author string `json:"author"` + // Jumpurl string `json:"jumpurl"` + // OriPicurl string `json:"ori_picurl"` + // } `json:"content"` + // Pos int `json:"pos"` + // More int `json:"more"` + // Selected string `json:"selected"` + // UsePlatform int `json:"use_platform"` + // } `json:"lan"` + // } `json:"info"` + // Extras struct { + // Name string `json:"name"` + // Transname string `json:"transname"` + // Subtitle string `json:"subtitle"` + // From string `json:"from"` + // Wikiurl string `json:"wikiurl"` + // } `json:"extras"` + TrackInfo struct { + ID int `json:"id"` + Type int `json:"type"` + Mid string `json:"mid"` + Name string `json:"name"` + Title string `json:"title"` + Subtitle string `json:"subtitle"` + Singer []struct { + ID int `json:"id"` + Mid string `json:"mid"` + Name string `json:"name"` + Title string `json:"title"` + Type int `json:"type"` + Uin int `json:"uin"` + } `json:"singer"` + Album struct { + ID int `json:"id"` + Mid string `json:"mid"` + Name string `json:"name"` + Title string `json:"title"` + Subtitle string `json:"subtitle"` + TimePublic string `json:"time_public"` + Pmid string `json:"pmid"` + } `json:"album"` + Mv struct { + ID int `json:"id"` + Vid string `json:"vid"` + Name string `json:"name"` + Title string `json:"title"` + Vt int `json:"vt"` + } `json:"mv"` + Interval int `json:"interval"` + Isonly int `json:"isonly"` + Language int `json:"language"` + Genre int `json:"genre"` + IndexCd int `json:"index_cd"` + IndexAlbum int `json:"index_album"` + TimePublic string `json:"time_public"` + Status int `json:"status"` + Fnote int `json:"fnote"` + File struct { + MediaMid string `json:"media_mid"` + Size24Aac int `json:"size_24aac"` + Size48Aac int `json:"size_48aac"` + Size96Aac int `json:"size_96aac"` + Size192Ogg int `json:"size_192ogg"` + Size192Aac int `json:"size_192aac"` + Size128Mp3 int `json:"size_128mp3"` + Size320Mp3 int `json:"size_320mp3"` + SizeApe int `json:"size_ape"` + SizeFlac int `json:"size_flac"` + SizeDts int `json:"size_dts"` + SizeTry int `json:"size_try"` + TryBegin int `json:"try_begin"` + TryEnd int `json:"try_end"` + URL string `json:"url"` + SizeHires int `json:"size_hires"` + HiresSample int `json:"hires_sample"` + HiresBitdepth int `json:"hires_bitdepth"` + B30S int `json:"b_30s"` + E30S int `json:"e_30s"` + Size96Ogg int `json:"size_96ogg"` + Size360Ra []interface{} `json:"size_360ra"` + SizeDolby int `json:"size_dolby"` + SizeNew []int `json:"size_new"` + } `json:"file"` + Pay struct { + PayMonth int `json:"pay_month"` + PriceTrack int `json:"price_track"` + PriceAlbum int `json:"price_album"` + PayPlay int `json:"pay_play"` + PayDown int `json:"pay_down"` + PayStatus int `json:"pay_status"` + TimeFree int `json:"time_free"` + } `json:"pay"` + Action struct { + Switch int `json:"switch"` + Msgid int `json:"msgid"` + Alert int `json:"alert"` + Icons int `json:"icons"` + Msgshare int `json:"msgshare"` + Msgfav int `json:"msgfav"` + Msgdown int `json:"msgdown"` + Msgpay int `json:"msgpay"` + Switch2 int `json:"switch2"` + Icon2 int `json:"icon2"` + } `json:"action"` + Ksong struct { + ID int `json:"id"` + Mid string `json:"mid"` + } `json:"ksong"` + Volume struct { + Gain float64 `json:"gain"` + Peak float64 `json:"peak"` + Lra float64 `json:"lra"` + } `json:"volume"` + Label string `json:"label"` + URL string `json:"url"` + Bpm int `json:"bpm"` + Version int `json:"version"` + Trace string `json:"trace"` + DataType int `json:"data_type"` + ModifyStamp int `json:"modify_stamp"` + Pingpong string `json:"pingpong"` + Ppurl string `json:"ppurl"` + Tid int `json:"tid"` + Ov int `json:"ov"` + Sa int `json:"sa"` + Es string `json:"es"` + Vs []string `json:"vs"` + Vi []int `json:"vi"` + Ktag string `json:"ktag"` + Vf []float64 `json:"vf"` + } `json:"track_info"` +} + +func getMusicInfo(songMid string) (infoBody musicInfo, emsg string) { + infoReqBody := ztool.Str_FastConcat(`{"comm":{"ct":"19","cv":"1859","uin":"0"},"req":{"method":"get_song_detail_yqq","module":"music.pf_song_detail_svr","param":{"song_mid":"`, songMid, `","song_type":0}}}`) + var infoResp struct { + Code int `json:"code"` + // Ts int64 `json:"ts"` + // StartTs int64 `json:"start_ts"` + // Traceid string `json:"traceid"` + Req struct { + Code int `json:"code"` + Data musicInfo `json:"data"` + } `json:"req"` + } + err := signRequest(bytesconv.StringToBytes(infoReqBody), &infoResp) + if err != nil { + emsg = err.Error() + return //nil, err.Error() + } + if infoResp.Code != 0 || infoResp.Req.Code != 0 { + emsg = `获取音乐信息失败` + return //nil, `获取音乐信息失败` + } + infoBody = infoResp.Req.Data + return //infoBody.Req.Data, `` +} + +// func (infoBody *musicInfo) GetLink(songMid, strFileName string) (ourl, msg string) { +// var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin +// if uuin == `` { +// uuin = `1535153710` +// } +// requestBody := ztool.Str_FastConcat(`{"comm":{"authst":"`, uauthst, `","ct":"26","cv":"2010101","qq":"`, uuin, `","v":"2010101"},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{"filename":["`, strFileName, `"],"guid":"114514","loginflag":1,"platform":"20","songmid":["`, songMid, `"],"songtype":[0],"uin":"10086"}}}`) +// var infoResp struct { +// Code int `json:"code"` +// // Ts int64 `json:"ts"` +// // StartTs int64 `json:"start_ts"` +// // Traceid string `json:"traceid"` +// Req0 playInfo `json:"req_0"` +// } +// err := signRequest(bytesconv.StringToBytes(requestBody), &infoResp) +// if err != nil { +// msg = err.Error() +// return +// } +// infoData := infoResp.Req0.Data.Midurlinfo[0] +// if infoData.Purl == `` { +// msg = `无法获取音乐链接` +// return +// } +// ourl = infoData.Purl +// return +// } diff --git a/src/sources/custom/tx/player.go b/src/sources/custom/tx/player.go new file mode 100644 index 0000000..fd892a1 --- /dev/null +++ b/src/sources/custom/tx/player.go @@ -0,0 +1,137 @@ +package tx + +import ( + "lx-source/src/env" + "strings" + + "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/x/bytesconv" +) + +type playInfo struct { + Code int `json:"code"` + Data struct { + Uin string `json:"uin"` + Retcode int `json:"retcode"` + VerifyType int `json:"verify_type"` + LoginKey string `json:"login_key"` + Msg string `json:"msg"` + Sip []string `json:"sip"` + Thirdip []string `json:"thirdip"` + Testfile2G string `json:"testfile2g"` + Testfilewifi string `json:"testfilewifi"` + Midurlinfo []struct { + Songmid string `json:"songmid"` + Filename string `json:"filename"` + Purl string `json:"purl"` + Errtype string `json:"errtype"` + P2Pfromtag int `json:"p2pfromtag"` + Qmdlfromtag int `json:"qmdlfromtag"` + CommonDownfromtag int `json:"common_downfromtag"` + VipDownfromtag int `json:"vip_downfromtag"` + Pdl int `json:"pdl"` + Premain int `json:"premain"` + Hisdown int `json:"hisdown"` + Hisbuy int `json:"hisbuy"` + UIAlert int `json:"uiAlert"` + Isbuy int `json:"isbuy"` + Pneedbuy int `json:"pneedbuy"` + Pneed int `json:"pneed"` + Isonly int `json:"isonly"` + Onecan int `json:"onecan"` + Result int `json:"result"` + Tips string `json:"tips"` + Opi48Kurl string `json:"opi48kurl"` + Opi96Kurl string `json:"opi96kurl"` + Opi192Kurl string `json:"opi192kurl"` + Opiflackurl string `json:"opiflackurl"` + Opi128Kurl string `json:"opi128kurl"` + Opi192Koggurl string `json:"opi192koggurl"` + Wififromtag string `json:"wififromtag"` + Flowfromtag string `json:"flowfromtag"` + Wifiurl string `json:"wifiurl"` + Flowurl string `json:"flowurl"` + Vkey string `json:"vkey"` + Opi30Surl string `json:"opi30surl"` + Ekey string `json:"ekey"` + AuthSwitch int `json:"auth_switch"` + Subcode int `json:"subcode"` + Opi96Koggurl string `json:"opi96koggurl"` + AuthSwitch2 int `json:"auth_switch2"` + } `json:"midurlinfo"` + Servercheck string `json:"servercheck"` + Expiration int `json:"expiration"` + } `json:"data"` +} + +/* + 音乐URL获取逻辑: + if需要付费播放and无账号信息: + 试听获取 + el有账号信息or无需付费: + 正常获取 + if没有链接: + 尝试获取试听 + if还没有链接: + 报错 + 返回结果 + 注: + 以上逻辑暂时没想好怎么改, + 当前根据配置文件 [Custom].Tx_Enable 是否开启判断, + if需要付费播放and未开启账号解析: + 试听获取 + el无需付费or有账号信息: + 正常获取 + if没有链接: + 报错 + 返回结果 +*/ + +func Url(songMid, quality string) (ourl, msg string) { + infoFile, ok := fileInfo[quality] + if !ok { + msg = `不支持的音质` + return + } + infoBody, emsg := getMusicInfo(songMid) + if emsg != `` { + msg = emsg + return + } + var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin + if uuin == `` { + uuin = `1535153710` + } + var strFileName string + tryLink := infoBody.TrackInfo.Pay.PayPlay == 1 && /*uauthst == ``&&*/ !env.Config.Custom.Tx_Enable + if tryLink { + strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.mp3`) + } else { + strFileName = ztool.Str_FastConcat(infoFile.H, infoBody.TrackInfo.File.MediaMid, infoFile.E) + } + requestBody := ztool.Str_FastConcat(`{"comm":{"authst":"`, uauthst, `","ct":"26","cv":"2010101","qq":"`, uuin, `","v":"2010101"},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{"filename":["`, strFileName, `"],"guid":"114514","loginflag":1,"platform":"20","songmid":["`, songMid, `"],"songtype":[0],"uin":"10086"}}}`) + var infoResp struct { + Code int `json:"code"` + // Ts int64 `json:"ts"` + // StartTs int64 `json:"start_ts"` + // Traceid string `json:"traceid"` + Req0 playInfo `json:"req_0"` + } + err := signRequest(bytesconv.StringToBytes(requestBody), &infoResp) + if err != nil { + msg = err.Error() + return + } + infoData := infoResp.Req0.Data.Midurlinfo[0] + if infoData.Purl == `` { + msg = `无法获取音乐链接` + return + } + realQuality := strings.Split(infoData.Filename, `.`)[0][:4] + if qualityMapReverse[realQuality] != quality && /*infoBody.TrackInfo.Pay.PayPlay == 0*/ !tryLink { + msg = `实际音质不匹配` + return + } + ourl = ztool.Str_FastConcat(`https://ws.stream.qqmusic.qq.com/`, infoData.Purl) + return +} diff --git a/src/sources/custom/tx/utils.go b/src/sources/custom/tx/utils.go new file mode 100644 index 0000000..1a85177 --- /dev/null +++ b/src/sources/custom/tx/utils.go @@ -0,0 +1,65 @@ +package tx + +import ( + "bytes" + "lx-source/src/sources" + "net/http" + + "github.com/ZxwyWebSite/ztool" +) + +var ( + fileInfo = map[string]struct { + E string + H string + }{ + sources.Q_128k: { + E: `.mp3`, + H: `M500`, + }, + sources.Q_320k: { + E: `.mp3`, + H: `M800`, + }, + sources.Q_flac: { + E: `.flac`, + H: `F000`, + }, + sources.Q_fl24: { + E: `.flac`, + H: `RS01`, + }, + `dolby`: { + E: `.flac`, + H: `Q000`, + }, + `master`: { + E: `.flac`, + H: `AI00`, + }, + } + qualityMapReverse = map[string]string{ + `M500`: sources.Q_128k, + `M800`: sources.Q_320k, + `F000`: sources.Q_flac, + `RS01`: sources.Q_fl24, + `Q000`: `dolby`, + `AI00`: `master`, + } +) + +func signRequest(data []byte, out any) error { + s := sign(data) + return ztool.Net_Request(http.MethodPost, + ztool.Str_FastConcat(`https://u.y.qq.com/cgi-bin/musics.fcg?format=json&sign=`, s), + bytes.NewReader(data), + []ztool.Net_ReqHandlerFunc{ + ztool.Net_ReqAddHeaders(map[string]string{ + `Referer`: `https://y.qq.com/`, + }), + }, + []ztool.Net_ResHandlerFunc{ + ztool.Net_ResToStruct(out), + }, + ) +} diff --git a/src/sources/custom/utils/utils.go b/src/sources/custom/utils/utils.go new file mode 100644 index 0000000..1cb76a5 --- /dev/null +++ b/src/sources/custom/utils/utils.go @@ -0,0 +1,12 @@ +package utils + +// func SizeFormat(size int) string { +// if size < 1024 { +// return ztool.Str_FastConcat(strconv.Itoa(size), `B`) +// } +// size64 := float64(size) +// if size64 < math.Pow(size64, 2) { + +// } +// return `` +// } diff --git a/src/sources/source.go b/src/sources/source.go index b16888a..56e0fb5 100644 --- a/src/sources/source.go +++ b/src/sources/source.go @@ -5,8 +5,21 @@ import ( ) // var Loger = env.Loger.NewGroup(`Sources`) // JieXiApis + const ( Err_Verify = `Verify Failed` + // 通用音质 + Q_128k = `128k` + Q_320k = `320k` + Q_flac = `flac` + Q_fl24 = `flac24bit` + // 通用平台 + S_wy = `wy` // 小芸 + S_mg = `mg` // 小蜜 + S_kw = `kw` // 小蜗 + S_kg = `kg` // 小枸 + S_tx = `tx` // 小秋 + S_lx = `lx` // 小洛 (预留) ) // 源查询接口 diff --git a/update.md b/update.md index 76e1678..4cd29c7 100644 --- a/update.md +++ b/update.md @@ -1,5 +1,11 @@ ## Lx-Source/更新日志 +#### 2023-12-31 v1.0.2-b0.8.2 (dev) +- 注:本次还是累积更新,不单独发布Release ++ 从Python版移植部分代码 ++ (兼容tx源一分钟试听链接获取) ++ 优化速率限制相关逻辑 + #### 2023-12-30 v1.0.2-b0.8.1 (dev) + 注:本次累积更新,不单独发布Release + 实现单ip速率限制,配置方法: