From 32c2617f3f35aad8501e1b571dbb245e52183453 Mon Sep 17 00:00:00 2001 From: ZxwyWebSite Date: Thu, 1 Feb 2024 14:52:43 +0800 Subject: [PATCH] 2024-01-31 v1.0.2-b12-d3 --- go.mod | 8 +- go.sum | 17 +- init.go | 176 ++++++++++++++ main.go | 228 +++++++----------- menu.go | 31 +++ readme.md | 15 +- src/caches/cache.go | 14 +- src/caches/localcache/local.go | 36 ++- src/env/env.go | 123 +++++++--- src/middleware/auth/auth.go | 51 ++-- src/middleware/resp/resp.go | 2 +- src/middleware/util/util.go | 40 ++- src/server/app_lxmusic.go | 59 +++++ src/server/app_musicfree.go | 34 +++ .../loadpublic => server}/loadpublic.go | 40 ++- src/server/loadquality.go | 56 +++++ .../loadpublic => server}/public/icon.ico | Bin .../public/lx-custom-source.js | 23 +- src/server/public/lx-icon.ico | Bin 0 -> 165662 bytes .../loadpublic => server}/public/test.txt | 0 src/{router => server}/router.go | 70 ++---- src/sources/builtin/driver.go | 25 +- src/sources/builtin/types.go | 7 +- src/sources/custom/kw/player.go | 4 +- src/sources/custom/tx/player.go | 1 + src/sources/custom/wy/init.go | 95 ++++---- .../wy/{encrypt.go => modules/core_crypto.go} | 17 +- .../wy/{utils.go => modules/core_request.go} | 4 +- src/sources/custom/wy/modules/core_types.go | 84 +++++++ .../custom/wy/{ => modules}/login_refresh.go | 0 .../custom/wy/{ => modules}/song_url.go | 0 .../custom/wy/{ => modules}/song_url_v1.go | 0 src/sources/custom/wy/player.go | 199 ++++++++------- src/sources/source.go | 42 +++- src/sources/types.go | 46 ++++ update.md | 55 +++++ 36 files changed, 1119 insertions(+), 483 deletions(-) create mode 100644 init.go create mode 100644 menu.go create mode 100644 src/server/app_lxmusic.go create mode 100644 src/server/app_musicfree.go rename src/{middleware/loadpublic => server}/loadpublic.go (63%) create mode 100644 src/server/loadquality.go rename src/{middleware/loadpublic => server}/public/icon.ico (100%) rename src/{middleware/loadpublic => server}/public/lx-custom-source.js (87%) create mode 100644 src/server/public/lx-icon.ico rename src/{middleware/loadpublic => server}/public/test.txt (100%) rename src/{router => server}/router.go (81%) rename src/sources/custom/wy/{encrypt.go => modules/core_crypto.go} (87%) rename src/sources/custom/wy/{utils.go => modules/core_request.go} (99%) create mode 100644 src/sources/custom/wy/modules/core_types.go rename src/sources/custom/wy/{ => modules}/login_refresh.go (100%) rename src/sources/custom/wy/{ => modules}/song_url.go (100%) rename src/sources/custom/wy/{ => modules}/song_url_v1.go (100%) create mode 100644 src/sources/types.go diff --git a/go.mod b/go.mod index f4375f5..6be21ab 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( require ( github.com/bytedance/sonic v1.10.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect @@ -21,7 +21,7 @@ require ( github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -29,10 +29,10 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.3.0 // indirect + golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 32137ca..2f9601f 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,9 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -45,8 +46,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -98,8 +99,8 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -110,11 +111,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/init.go b/init.go new file mode 100644 index 0000000..0ffd99b --- /dev/null +++ b/init.go @@ -0,0 +1,176 @@ +package main + +import ( + "encoding/base64" + "lx-source/src/caches" + "lx-source/src/caches/localcache" + "lx-source/src/env" + "lx-source/src/sources" + "lx-source/src/sources/builtin" + "net/http" + stdurl "net/url" + "path/filepath" + + "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/zcypt" + "github.com/gin-gonic/gin" +) + +// 生成连接码 +func genAuth() { + ga := env.Loger.NewGroup(`LxM-Auth`) + // 检测Key是否存在, 否则生成并保存 + if env.Config.Auth.ApiKey_Value == `` { + pass := zcypt.Base64ToString(base64.StdEncoding, zcypt.RandomBytes(4*4)) + env.Config.Auth.ApiKey_Value = pass // env.Config.Apis.LxM_Auth + ga.Info(`已生成默认连接码: %q`, pass) + ga.Info(`可在配置文件 [Auth].ApiKey_Value 中修改`) //可在配置文件 [Apis].LxM_Auth 中修改, 填写 "null" 关闭验证 + if err := env.Cfg.Save(``); err != nil { + ga.Error(`写入配置文件失败: %s, 将导致下次启动时连接码发生变化`, err) + } + } + if !env.Config.Auth.ApiKey_Enable { + ga.Warn(`已关闭Key验证, 公开部署可能导致安全隐患`) + } else { + ga.Warn(`已开启Key验证, 记得在脚本中填写 apipass=%q`, env.Config.Auth.ApiKey_Value) + } + ga.Free() +} + +// 加载文件日志 (请在初始化配置文件后调用) +func loadFileLoger() { + // 最后加载FileLoger保证必要日志已输出 (Debug模式强制在控制台输出日志) + if env.Config.Main.LogPath != `` { + lg := env.Loger.NewGroup(`FileLoger`) + printout := env.Config.Main.Print // || env.Config.Main.Debug + _, do, err := env.Loger.SetOutFile(ztool.Str_FastConcat(env.RunPath, env.Config.Main.LogPath), printout) + if err == nil { + env.Defer.Add(do) + gin.DefaultWriter = env.Loger.GetOutput() + gin.ForceConsoleColor() + // lg.Info(`文件日志初始化成功`) + } else { + lg.Error(`文件日志初始化失败:%v`, err) + } + lg.Free() + } +} + +// 初始化基础功能 +func initMain() { + // 初始化代理 + ipr := env.Loger.NewGroup(`InitProxy`) + switch env.Config.Source.FakeIP_Mode { + case `0`, `off`: + break + case `1`, `req`: + ipr.Fatal(`暂未实现此功能`) + case `2`, `val`: + if env.Config.Source.FakeIP_Value != `` { + ipr.Info(`已开启伪装IP,当前值: %v`, env.Config.Source.FakeIP_Value) + ztool.Net_header[`X-Real-IP`] = env.Config.Source.FakeIP_Value + ztool.Net_header[`X-Forwarded-For`] = env.Config.Source.FakeIP_Value + } else { + ipr.Error(`伪装IP为空,请检查配置 [Source].FakeIP_Value`) + } + default: + ipr.Error(`未定义的代理模式,请检查配置 [Source].FakeIP_Mode,本次启动禁用IP伪装`) + } + if env.Config.Source.Proxy_Enable { + ipr.Debug(`ProxyAddr: %v`, env.Config.Source.Proxy_Address) + addr, err := stdurl.Parse(env.Config.Source.Proxy_Address) + if err != nil { + ipr.Error(`代理Url解析失败: %s, 将禁用代理功能`, err) + } else { + type chkRegion struct { + AmapFlag int `json:"amap_flag"` + IPFlag int `json:"ip_flag"` + AmapAddress string `json:"amap_address"` + Country string `json:"country"` + Flag int `json:"flag"` + Errcode int `json:"errcode"` + Status int `json:"status"` + Error string `json:"error"` + } + var out chkRegion + oldval := ztool.Net_client.Transport + ztool.Net_client.Transport = &http.Transport{Proxy: http.ProxyURL(addr)} + err := ztool.Net_Request(http.MethodGet, + `https://mips.kugou.com/check/iscn?&format=json`, nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(ztool.Net_header)}, + []ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out)}, + ) + if err != nil { + ztool.Net_client.Transport = oldval + ipr.Error(`地区验证失败: %s, 已恢复默认配置`, err) + } else { + ipr.Debug(`Resp: %+v`, out) + if out.Flag != 1 { + ipr.Warn(`您正在使用非中国大陆(%v)代理,可能导致部分音乐不可用`, out.Country) + } else { + ipr.Warn(`代理开启成功,请注意潜在的Cookie泄露问题`) + } + } + } + } + ipr.Free() + + // 初始化缓存 + icl := env.Loger.NewGroup(`InitCache`) + switch env.Config.Cache.Mode { + case `0`, `off`: + // NothingToDo... (已默认禁用缓存) + break + case `1`, `local`: + // 注:由于需要修改LocalCachePath参数,必须在InitRouter之前执行 + cache, err := caches.New(&localcache.Cache{ + Path: filepath.Join(env.RunPath, env.Config.Cache.Local_Path), + Bind: env.Config.Cache.Local_Bind, + }) + if err != nil { + icl.Error(`驱动["local"]初始化失败: %v, 将禁用缓存功能`, err) + } + caches.UseCache = cache + icl.Warn(`本地缓存绑定地址:%q,请确认其与实际访问地址相符`, env.Config.Cache.Local_Bind) + // LocalCachePath = filepath.Join(runPath, env.Config.Cache.Local_Path) + // UseCache = &localcache.Cache{ + // Path: LocalCachePath, + // Addr: env.Config.Apis.BindAddr, + // } + // icl.Info(`使用本地缓存,文件路径 %q,绑定地址 %v`, LocalCachePath, env.Config.Apis.BindAddr) + case `2`, `cloudreve`: + icl.Fatal(`Cloudreve驱动暂未完善,未兼容新版调用方式,当前版本禁用`) + // icl.Warn(`Cloudreve驱动暂未完善,使用非本机存储时存在兼容性问题,请谨慎使用`) + // cs, err := cloudreve.NewSite(&cloudreve.Config{ + // SiteUrl: env.Config.Cache.Cloud_Site, + // Username: env.Config.Cache.Cloud_User, + // Password: env.Config.Cache.Cloud_Pass, + // Session: env.Config.Cache.Cloud_Sess, + // }) + // if err != nil { + // icl.Error(`驱动["cloudreve"]初始化失败: %v, 将禁用缓存功能`, err) + // } + // UseCache = &crcache.Cache{ + // Cs: cs, + // Path: env.Config.Cache.Cloud_Path, + // IsOk: err == nil, + // } + default: + icl.Error(`未定义的缓存模式,请检查配置 [Cache].Mode,本次启动禁用缓存`) + } + icl.Free() + + // 初始化音乐源 + ise := env.Loger.NewGroup(`InitSource`) + switch env.Config.Source.Mode { + case `0`, `off`: + break + case `1`, `builtin`: + sources.UseSource = &builtin.Source{} + case `2`, `custom`: + ise.Fatal(`暂未实现账号解析源`) + default: + ise.Error(`未定义的音乐源,请检查配置 [Source].Mode,本次启动禁用内置源`) + } + ise.Free() +} diff --git a/main.go b/main.go index f468a50..5db817f 100644 --- a/main.go +++ b/main.go @@ -2,19 +2,16 @@ package main import ( "context" - "encoding/base64" "flag" - "lx-source/src/caches" - "lx-source/src/caches/localcache" + "io/fs" "lx-source/src/env" - "lx-source/src/router" - "lx-source/src/sources" - "lx-source/src/sources/builtin" - "math/rand" + "lx-source/src/server" "net/http" "os" "os/signal" - "path/filepath" + "strings" + "sync" + "sync/atomic" "syscall" "time" @@ -23,50 +20,6 @@ import ( "github.com/gin-gonic/gin" ) -func genAuth() { - ga := env.Loger.NewGroup(`LxM-Auth`) - // 检测Key是否存在, 否则生成并保存 - if env.Config.Auth.ApiKey_Value == `` { - randomBytes := func(size int) []byte { - buf := make([]byte, size) - for i := 0; i < size; i++ { - buf[i] = byte(rand.New(rand.NewSource(time.Now().UnixNano() + int64(i*i+rand.Intn(256)))).Intn(256)) - } - return buf - } - pass := base64.StdEncoding.EncodeToString(randomBytes(4 * 4)) - env.Config.Auth.ApiKey_Value = pass // env.Config.Apis.LxM_Auth - ga.Info(`已生成默认连接码: %q`, pass) - ga.Info(`可在配置文件 [Auth].ApiKey_Value 中修改`) //可在配置文件 [Apis].LxM_Auth 中修改, 填写 "null" 关闭验证 - if err := env.Cfg.Save(``); err != nil { - ga.Error(`写入配置文件失败: %s, 将导致下次启动时连接码发生变化`, err) - } - } - if !env.Config.Auth.ApiKey_Enable { - ga.Warn(`已关闭Key验证, 公开部署可能导致安全隐患`) - } else { - ga.Warn(`已开启Key验证, 记得在脚本中填写 apipass=%q`, env.Config.Auth.ApiKey_Value) - } -} - -// 加载文件日志 (请在初始化配置文件后调用) -func loadFileLoger() { - // 最后加载FileLoger保证必要日志已输出 (Debug模式强制在控制台输出日志) - if env.Config.Main.LogPath != `` { - lg := env.Loger.NewGroup(`FileLoger`) - printout := env.Config.Main.Print // || env.Config.Main.Debug - _, do, err := env.Loger.SetOutFile(ztool.Str_FastConcat(env.RunPath, env.Config.Main.LogPath), printout) - if err == nil { - env.Defer.Add(do) - gin.DefaultWriter = env.Loger.GetOutput() - gin.ForceConsoleColor() - // lg.Info(`文件日志初始化成功`) - } else { - lg.Error(`文件日志初始化失败:%v`, err) - } - } -} - // 初始化 func init() { ztool.Cmd_FastPrint(ztool.Str_FastConcat(` @@ -82,18 +35,26 @@ func init() { env.RunPath, _ = os.Getwd() var confPath string flag.StringVar(&confPath, `c`, ztool.Str_FastConcat(env.RunPath, `/data/conf.ini`), `指定配置文件路径`) + etag := flag.String(`e`, ``, `扩展启动参数`) + perm := flag.Uint(`p`, 0, `自定义文件权限(8进制前面加0)`) flag.Parse() - // fileLoger() // 注:记录日志必然会影响性能,自行选择是否开启 - // logs.DefLogger(`LX-SOURCE`, logs.LevelDebu) - // logs.Main = `LX-SOURCE` - env.Cfg.MustInit(confPath) //conf.InitConfig(confPath) + if perm != nil && *perm != 0 { + ztool.Fbj_DefPerm = fs.FileMode(*perm) + fp := env.Loger.NewGroup(`FilePerm`) + // if ztool.Fbj_DefPerm > 777 { + // fp.Fatal(`请在实际权限前面加0`) + // } + fp.Info(`设置默认文件权限为 %o (%v)`, *perm, ztool.Fbj_DefPerm).Free() + } + parseEtag(etag) + env.Cfg.MustInit(confPath) // fmt.Printf("%+v\n", env.Config) - env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`) + env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`).Free() if !env.Config.Main.Debug { gin.SetMode(gin.ReleaseMode) } else { logs.Levell = logs.LevelDebu // logs.Level = 3 - env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`) + env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`).Free() } genAuth() if env.Config.Main.SysLev { @@ -103,102 +64,93 @@ func init() { } else { sl.Warn(`成功设置较高优先级,此功能可能导致系统不稳定`) } + sl.Free() + } + if env.Config.Main.Timeout != env.DefCfg.Main.Timeout { + ztool.Net_client.Timeout = time.Second * time.Duration(env.Config.Main.Timeout) // 自定义请求超时 + env.Loger.NewGroup(`InitNet`).Info(`请求超时已设为 %s`, ztool.Net_client.Timeout).Free() } } func main() { defer env.Defer.Do() - - // 初始化缓存 - icl := env.Loger.NewGroup(`InitCache`) - switch env.Config.Cache.Mode { - case `0`, `off`: - // NothingToDo... (已默认禁用缓存) - break - case `1`, `local`: - // 注:由于需要修改LocalCachePath参数,必须在InitRouter之前执行 - cache, err := caches.New(&localcache.Cache{ - Path: filepath.Join(env.RunPath, env.Config.Cache.Local_Path), - Bind: env.Config.Cache.Local_Bind, - }) - if err != nil { - icl.Error(`驱动["local"]初始化失败: %v, 将禁用缓存功能`, err) - } - caches.UseCache = cache - icl.Warn(`本地缓存绑定地址: %q, 请确认其与实际访问地址相符`, env.Config.Cache.Local_Bind) - // LocalCachePath = filepath.Join(runPath, env.Config.Cache.Local_Path) - // UseCache = &localcache.Cache{ - // Path: LocalCachePath, - // Addr: env.Config.Apis.BindAddr, - // } - // icl.Info(`使用本地缓存,文件路径 %q,绑定地址 %v`, LocalCachePath, env.Config.Apis.BindAddr) - case `2`, `cloudreve`: - icl.Fatal(`Cloudreve驱动暂未完善,未兼容新版调用方式,当前版本禁用`) - // icl.Warn(`Cloudreve驱动暂未完善,使用非本机存储时存在兼容性问题,请谨慎使用`) - // cs, err := cloudreve.NewSite(&cloudreve.Config{ - // SiteUrl: env.Config.Cache.Cloud_Site, - // Username: env.Config.Cache.Cloud_User, - // Password: env.Config.Cache.Cloud_Pass, - // Session: env.Config.Cache.Cloud_Sess, - // }) - // if err != nil { - // icl.Error(`驱动["cloudreve"]初始化失败: %v, 将禁用缓存功能`, err) - // } - // UseCache = &crcache.Cache{ - // Cs: cs, - // Path: env.Config.Cache.Cloud_Path, - // IsOk: err == nil, - // } - default: - icl.Error(`未定义的缓存模式,请检查配置 [Cache].Mode,本次启动禁用缓存`) - } - - // 初始化音乐源 - ise := env.Loger.NewGroup(`InitSource`) - switch env.Config.Source.Mode { - case `0`, `off`: - break - case `1`, `builtin`: - sources.UseSource = &builtin.Source{} - case `2`, `custom`: - ise.Fatal(`暂未实现账号解析源`) - default: - ise.Error(`未定义的音乐源,请检查配置 [Source].Mode,本次启动禁用内置源`) - } + // 初始化基础功能 + initMain() // 载入必要模块 env.Inits.Do() - env.Loger.NewGroup(`ServStart`).Info(`服务端启动, 监听地址 %s`, env.Config.Main.Listen) + env.Loger.NewGroup(`ServInit`).Info(`服务端启动, 监听地址 %s`, strings.Join(env.Config.Main.Listen, `|`)).Free() loadFileLoger() env.Defer.Add(env.Tasker.Run(env.Loger)) // wait // 启动Http服务 - r := router.InitRouter() //InitRouter() - server := &http.Server{ - Addr: env.Config.Main.Listen, - Handler: r, - } - go func() { - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - env.Loger.NewGroup(`InitRouter().Run`).Fatal(`%s`, err) - } - }() + listenAndServe(server.InitRouter(), env.Config.Main.Listen) +} +// 监听多端口 +func listenAndServe(handler http.Handler, addrs []string) { + // 前置检测 + length := len(addrs) + ss := env.Loger.NewGroup(`ServStart`) + if length == 0 { + ss.Fatal(`监听地址列表为空`) + } + // ss.Info(`服务端启动,请稍候...`) + srvlist := make(map[int]*http.Server, length) // 伪数组,便于快速删除数据 + lock := new(sync.Mutex) + var failnum int32 + length32 := int32(length) + // 启动服务 + for i := 0; i < length; i++ { + lock.Lock() + srvlist[i] = &http.Server{Addr: addrs[i], Handler: handler} + lock.Unlock() + go func(n int) { + server := srvlist[n] + // ss.Info(`开始监听 %v`, server.Addr) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + ss.Error(`监听%q失败: %s`, server.Addr, err) // 监听":1011"失败: http: Server closed + fn := atomic.AddInt32(&failnum, 1) + if fn == length32 { + ss.Fatal(`所有地址监听失败,程序被迫退出`) + } + lock.Lock() + delete(srvlist, n) + lock.Unlock() + } + }(i) + } + // time.Sleep(time.Millisecond * 300) + // if len(srvlist) == 0 { + // ss.Fatal(`所有地址监听失败,程序被迫退出`) + // } + // ss.Free() + // 安全退出 quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) <-quit sc := env.Loger.NewGroup(`ServClose`) - sc.Info(`等待结束活动连接...`) // Shutdown Server ... - - ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - sc.Fatal(`未安全退出: %s`, err) // Server Shutdown + sc.Info(`等待结束活动连接...`) + // 停止服务 + var unsafenum int32 + wg := new(sync.WaitGroup) + for i := range srvlist { + wg.Add(1) + go func(n int) { + server := srvlist[n] + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) + if err := server.Shutdown(ctx); err != nil { + sc.Warn(`连接%q未安全退出: %s`, server.Addr, err) // 连接":1011"未安全退出: timeout + atomic.AddInt32(&unsafenum, 1) + } + cancel() + wg.Done() + }(i) + } + wg.Wait() + if unsafenum != 0 { + sc.Warn(`未安全退出 :(`) + } else { + sc.Info(`已安全退出 :)`) } - sc.Info(`已安全退出 :)`) // Server exited - - // if err := InitRouter().Run(env.Config.Main.Listen); err != nil { - // env.Loger.NewGroup(`InitRouter().Run`).Fatal(`%s`, err) - // } } diff --git a/menu.go b/menu.go new file mode 100644 index 0000000..70da36d --- /dev/null +++ b/menu.go @@ -0,0 +1,31 @@ +package main + +import ( + "lx-source/src/env" +) + +func parseEtag(etag *string) { + if etag == nil { + return + } + loger := env.Loger.NewGroup(`ParseEtag`) + switch *etag { + case ``: + break + case `menu`: + loger.Fatal(`暂不支持交互菜单,敬请期待...`) + // menuMian() + default: + loger.Fatal(`未知参数:%q`, *etag) + } + loger.Free() +} + +// func menuMian() { +// app := menu.NewApp(`Lx-Source`) +// app.Data = menu.Data{ +// `Main`: func(this *menu.App) string { return ` ` }, +// } +// app.Run() +// os.Exit(0) +// } diff --git a/readme.md b/readme.md index f1a1323..6d9d97d 100644 --- a/readme.md +++ b/readme.md @@ -11,11 +11,11 @@ ## ZxwyWebSite/LX-Source ### 简介 + LX-Music 解析源 (洛雪音乐自定义源) -+ **由于本项目的特殊性,请低调使用,切勿宣传** ++ **由于本项目的特殊性,请低调使用,切勿大肆宣传** + 测试阶段,不代表最终品质 + 验证部分暂未完善,建议仅本地部署,不要公开发布 + 视频教程:[使用教程.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) - + ### 使用 #### 服务端 @@ -48,8 +48,8 @@ ### 音乐源 -+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用** -+ 账号源 (登录Vip账号解析) **注:可能导致封号,如出问题本项目不负责** ++ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用,否则停止后续更新** ++ 账号源 (登录会员账号解析) **注:可能导致封号,如出问题本项目不负责** ### 开发 + 环境要求:Golang 1.21 (建议 >=1.20) @@ -61,18 +61,21 @@ - pkg/ 依赖包,一般在外部调用,不轻易修改 - src/ 源码包,用于实现各种功能 * env 公用变量,需要全局调用的参数 + * database 数据库相关 * caches 文件缓存封装 - * router Gin路由 + * server Gin路由 * middleware 请求中间件 * sources 音乐源 - build.go 快速构建脚本 (请先根据本地环境编辑配置) + - init.go 初始化检测 + - menu.go 交互菜单 (暂未实现) - main.go 主程序 ### 其它 + 基于 Golang + Gin框架 编写 -+ 部分功能参考 [Python版](https://github.com/lxmusics/lx-music-api-server-python) 实现 ++ 感谢以下项目提供参考:[Python版](https://github.com/lxmusics/lx-music-api-server-python),[WyApi](https://github.com/ZxwyWebSite/NeteaseCloudMusicApi),... ### 更新 + 见 `update.md` diff --git a/src/caches/cache.go b/src/caches/cache.go index 97905a9..25c0d74 100644 --- a/src/caches/cache.go +++ b/src/caches/cache.go @@ -1,10 +1,13 @@ package caches import ( + "lx-source/src/env" + "net/http" "strings" "sync" "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/logs" ) type ( @@ -15,6 +18,7 @@ type ( Quality string // quality 音质 128k / 320k / flac / flac24bit Extname string // rext 扩展名 mp3 / flac (没有前缀点) query string // 查询字符串缓存 + Request *http.Request } // 缓存需实现以下接口 Cache interface { @@ -40,7 +44,6 @@ func (*Nullcache) Stat() bool { return false } func (*Nullcache) Init() error { return nil } var ( - // Loger = env.Loger.NewGroup(`Caches`) UseCache Cache = &Nullcache{} // ErrNotInited = errors.New(`缓存策略未初始化`) query_pool = sync.Pool{New: func() any { return new(Query) }} @@ -111,3 +114,12 @@ func MustNew(c Cache) Cache { } return out } + +var Loger *logs.Logger + +// 初始化Loger +func init() { + env.Inits.Add(func() { + Loger = env.Loger.NewGroup(`Caches`) + }) +} diff --git a/src/caches/localcache/local.go b/src/caches/localcache/local.go index dfe9997..5ce3422 100644 --- a/src/caches/localcache/local.go +++ b/src/caches/localcache/local.go @@ -4,9 +4,11 @@ import ( "errors" "lx-source/src/caches" "lx-source/src/env" + "lx-source/src/middleware/util" "net/url" "os" "strings" + "time" "github.com/ZxwyWebSite/ztool" ) @@ -17,10 +19,17 @@ type Cache struct { state bool // 激活状态 } -var loger = env.Loger.NewGroup(`Caches`) //caches.Loger.AppGroup(`local`) +// var loger = env.Loger.NewGroup(`Caches`) //caches.Loger.AppGroup(`local`) -func (c *Cache) getLink(q string) string { - return ztool.Str_FastConcat(c.Bind, `/file/`, q) // c.Addr + `file/` + q +func (c *Cache) getLink(q *caches.Query) (furl string) { + // fmt.Printf("%#v\n", q.Request) + if env.Config.Cache.Local_Auto { + // 注:此方式无法确定是否支持HTTPS,暂时默认HTTP,让Nginx重定向 + furl = ztool.Str_FastConcat(util.GetPath(q.Request, `link/`), `file/`, q.Query()) + } else { + furl = ztool.Str_FastConcat(c.Bind, `/file/`, q.Query()) + } + return } func (c *Cache) Get(q *caches.Query) string { @@ -31,7 +40,7 @@ func (c *Cache) Get(q *caches.Query) string { } // env.Cache.Set(q.Query(), struct{}{}, 3600) // } - return c.getLink(q.Query()) + return c.getLink(q) // fpath := filepath.Join(c.Path, q.Source, q.MusicID, q.Quality) // if _, e := os.Stat(fpath); e != nil { // return `` @@ -54,14 +63,23 @@ func (c *Cache) Set(q *caches.Query, l string) string { // } // loger.Debug(`FFMpeg_Out: %v`, out) // } else { - err := ztool.Net_DownloadFile(l, fpath, nil) - if err != nil { - loger.Error(`DownloadFile: %v`, err) - return `` + for i := 0; true; i++ { + err := ztool.Net_DownloadFile(l, fpath, nil) + if err == nil { + break + } + caches.Loger.Error(`DownloadFile: %v, Retry: %v`, err, i) + if i == 1 || !strings.Contains(err.Error(), `context deadline exceeded`) { + if err := os.Remove(fpath); err != nil { + caches.Loger.Error(`RemoveFile: %s`, err) + } + return `` + } + time.Sleep(time.Second) } // } // env.Cache.Set(q.Query(), struct{}{}, 3600) - return c.getLink(q.Query()) + return c.getLink(q) // fpath := filepath.Join(c.Path, q.String) // os.MkdirAll(filepath.Dir(fpath), fs.ModePerm) // g := c.Loger.NewGroup(`localcache`) diff --git a/src/env/env.go b/src/env/env.go index 45974b8..88aad1c 100644 --- a/src/env/env.go +++ b/src/env/env.go @@ -12,7 +12,7 @@ import ( ) const ( - Version = `1.0.2-b12` + Version = `1.0.2-dev` ) var ( @@ -26,20 +26,31 @@ var ( 序号 名称 描述 */ type ( + // 系统 Conf_Main struct { - Debug bool `comment:"调试模式"` - Listen string `comment:"监听地址"` - Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"` - LogPath string `comment:"文件日志路径,不填禁用"` - Print bool `comment:"控制台输出"` - SysLev bool `comment:"(实验性) 设置进程高优先级"` + Debug bool `comment:"调试模式"` + Listen []string `comment:"监听地址 (多端口以','分隔)"` + Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"` + LogPath string `comment:"文件日志路径,不填禁用"` + Print bool `comment:"控制台输出 (影响io性能,后台使用建议关闭)"` + SysLev bool `comment:"(实验性) 设置进程高优先级"` // FFConv bool `comment:"(实验性) 使用FFMpeg修复音频(本地缓存)"` + NgProxy bool `comment:"兼容反向代理(beta)"` + Timeout int64 `comment:"网络请求超时(单位:秒,海外服务器可适当调大)"` } + // 接口 Conf_Apis struct { // 预留:后期可能会出一个WebUI,/webui,相关设置 - // BindAddr string `comment:"外部访问地址,用于生成文件链接"` - // LxM_Auth string `comment:"验证Key,自动生成,填写null禁用"` + // WebUI + // WebUI_Enable bool `comment:"是否开启WebUI相关接口"` + // App + // App_Enable bool `comment:"是否开启软件接口"` + // App Lx-Music + // App_LX_Enable bool `comment:"是否开启Lx-Music相关接口"` + // App MusicFree + // App_MF_Enable bool `comment:"是否开启MusicFree相关接口"` } + // 验证 Conf_Auth struct { // ApiKey ApiKey_Enable bool `comment:"是否开启Key验证"` @@ -47,15 +58,16 @@ type ( // 速率限制 RateLimit_Enable bool `comment:"是否开启速率限制"` RateLimit_Block uint32 `comment:"检测范围,每分区为x秒"` // 每x秒一个分区 - RateLimit_Global uint32 `comment:"全局速率限制,单位次每x秒(暂未开放)"` + // RateLimit_Global uint32 `comment:"全局速率限制,单位次每x秒(暂未开放)"` RateLimit_Single uint32 `comment:"单IP速率限制,单位次每x秒"` RateLimit_BanNum uint32 `comment:"容忍限度,超出限制N次后封禁"` RateLimit_BanTim uint32 `comment:"封禁后每次延长时间"` // 黑白名单 - BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"` - BanList_White []string `comment:"host白名单"` - BanList_Black []string `comment:"host黑名单"` + // BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"` + // BanList_White []string `comment:"host白名单"` + // BanList_Black []string `comment:"host黑名单"` } + // 来源 Conf_Source struct { Mode string `comment:"音乐来源 0: off(关闭 仅本地), 1: builtin(内置), 2: custom(登录账号 暂不支持)"` // 伪装IP @@ -67,19 +79,32 @@ type ( // 验证 MusicIdVerify bool `comment:"(beta) 验证音乐ID可用性"` ForceFallback bool `comment:"忽略音质限制,强制获取试听音频"` + // 总开关(解决部分源无法彻底禁用问题)? + Enable_Wy bool `comment:"是否开启小芸源"` + Enable_Mg bool `comment:"是否开启小蜜源"` + Enable_Kw bool `comment:"是否开启小蜗源"` + Enable_Kg bool `comment:"是否开启小枸源"` + Enable_Tx bool `comment:"是否开启小秋源"` + Enable_Lx bool `comment:"是否开启小洛源"` } // `comment:""` + // 账号 Conf_Custom struct { // wy - Wy_Enable bool `comment:"是否开启小芸源"` - Wy_Cookie string `comment:"账号cookie数据"` - Wy_Refresh_Enable bool `comment:"是否启用刷新登录"` - Wy_Refresh_Interval int64 `comment:"下次刷新时间 (由程序维护)"` + Wy_Enable bool `comment:"是否启用小芸源"` + Wy_Mode string `comment:"获取方式 0: builtin, 1: 163api"` + // wy 163api + Wy_Api_Type string `comment:"调用方式 0: native(内置模块), 1: remote(指定地址)"` + Wy_Api_Address string `comment:"NeteaseCloudMusicApi项目地址"` + Wy_Api_Cookie string `comment:"账号cookie数据"` + // wy refresh + // Wy_Refresh_Enable bool `comment:"是否启用刷新登录"` + // Wy_Refresh_Interval int64 `comment:"下次刷新时间 (由程序维护)"` // mg (暂未实现) - // Mg_Enable bool `comment:"是否开启小蜜源"` + Mg_Enable bool `comment:"是否启用小蜜源"` // kw - Kw_Enable bool `comment:"是否开启小蜗源"` + Kw_Enable bool `comment:"是否启用小蜗源"` Kw_Mode string `comment:"接口模式 0: bdapi(需验证), 1: kwdes"` // kw bdapi Kw_Bd_Uid string `comment:"field user.uid"` @@ -90,35 +115,43 @@ type ( Kw_Des_Header string `comment:"请求头 User-Agent"` // kg (暂未实现) - // Kg_Enable bool `comment:"是否开启小枸源"` + Kg_Enable bool `comment:"是否启用小枸源"` // tx - Tx_Enable bool `comment:"是否开启小秋源"` + Tx_Enable bool `comment:"是否启用小秋源"` Tx_Ukey string `comment:"Cookie中/客户端的请求体中的(comm.authst)"` Tx_Uuin string `comment:"key对应的QQ号"` // tx refresh_login Tx_Refresh_Enable bool `comment:"是否启动刷新登录"` Tx_Refresh_Interval int64 `comment:"刷新间隔 (由程序维护,非必要无需修改)"` + + // lx (local) + // Lx_Enable bool `comment:"是否启用小洛源"` } + // 脚本 Conf_Script struct { Ver string `comment:"自定义脚本版本" json:"ver"` Log string `comment:"更新日志" json:"log"` Url string `comment:"脚本下载地址 (public目录内文件名)" json:"url"` Force bool `comment:"强制推送更新" json:"force"` + Auto int `comment:"自动填写配置(beta) 0: 关闭, 1: 仅api地址, 2: 包含密钥" json:"-"` } + // 缓存 Conf_Cache struct { Mode string `comment:"缓存模式 0: off(关闭), 1: local(本地), 2: cloudreve(云盘 未完善)"` LinkMode string `comment:"外链样式 1: static(永久链), 2: dynamic(临时链)"` // 本地 Local_Path string `comment:"本地缓存保存路径"` Local_Bind string `comment:"本地缓存外部访问地址"` + Local_Auto bool `comment:"自适应缓存访问地址(beta)"` // 云盘 - Cloud_Site string `comment:"Cloudreve站点地址"` - Cloud_User string `comment:"Cloudreve用户名"` - Cloud_Pass string `comment:"Cloudreve密码"` - Cloud_Sess string `comment:"Cloudreve会话"` - Cloud_Path string `comment:"Cloudreve存储路径"` + // Cloud_Site string `comment:"Cloudreve站点地址"` + // Cloud_User string `comment:"Cloudreve用户名"` + // Cloud_Pass string `comment:"Cloudreve密码"` + // Cloud_Sess string `comment:"Cloudreve会话"` + // Cloud_Path string `comment:"Cloudreve存储路径"` } + // 结构 Conf struct { Main Conf_Main `comment:"程序主配置"` Apis Conf_Apis `comment:"接口设置"` @@ -135,11 +168,12 @@ var ( DefCfg = Conf{ Main: Conf_Main{ Debug: false, - Listen: `127.0.0.1:1011`, + Listen: []string{`127.0.0.1:1011`}, Gzip: false, LogPath: `/data/logfile.log`, Print: true, SysLev: false, + Timeout: 30, }, Apis: Conf_Apis{ // BindAddr: `http://192.168.10.22:1011/`, @@ -148,12 +182,12 @@ var ( ApiKey_Enable: true, RateLimit_Enable: false, RateLimit_Block: 30, - RateLimit_Global: 1, + // RateLimit_Global: 1, RateLimit_Single: 15, RateLimit_BanNum: 5, RateLimit_BanTim: 10, - BanList_Mode: `off`, - BanList_White: []string{`127.0.0.1`}, + // BanList_Mode: `off`, + // BanList_White: []string{`127.0.0.1`}, }, Source: Conf_Source{ Mode: `builtin`, @@ -161,16 +195,29 @@ var ( FakeIP_Value: `192.168.10.2`, Proxy_Enable: false, Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`, + + Enable_Wy: true, + Enable_Mg: true, + Enable_Kw: true, + Enable_Kg: true, + Enable_Tx: true, + Enable_Lx: true, }, Custom: Conf_Custom{ - Wy_Enable: true, - Wy_Refresh_Interval: 1633622400, + Wy_Enable: true, + Wy_Mode: `builtin`, + Wy_Api_Type: `native`, + // Wy_Refresh_Interval: 1633622400, + + Mg_Enable: true, Kw_Enable: true, Kw_Mode: `kwdes`, Kw_Des_Type: `json`, Kw_Des_Header: `okhttp/3.10.0`, + Kg_Enable: true, + Tx_Enable: false, Tx_Refresh_Enable: false, Tx_Refresh_Interval: 86000, @@ -181,18 +228,18 @@ var ( Ver: `1.0.3`, // 自定义脚本版本 Force: true, // 强制推送更新 - Url: `public/lx-custom-source.js`, // 脚本下载地址 + Url: `lx-custom-source.js`, // 脚本下载地址 }, Cache: Conf_Cache{ Mode: `local`, // 缓存模式 LinkMode: `1`, Local_Path: `data/cache`, Local_Bind: `http://127.0.0.1:1011/`, - Cloud_Site: `https://cloudreveplus-demo.onrender.com/`, - Cloud_User: `admin@cloudreve.org`, - Cloud_Pass: `CloudrevePlusDemo`, - Cloud_Sess: ``, - Cloud_Path: `/Lx-Source/cache`, + // Cloud_Site: `https://cloudreveplus-demo.onrender.com/`, + // Cloud_User: `admin@cloudreve.org`, + // Cloud_Pass: `CloudrevePlusDemo`, + // Cloud_Sess: ``, + // Cloud_Path: `/Lx-Source/cache`, }, } Config = DefCfg diff --git a/src/middleware/auth/auth.go b/src/middleware/auth/auth.go index de1a06f..063f41a 100644 --- a/src/middleware/auth/auth.go +++ b/src/middleware/auth/auth.go @@ -19,19 +19,6 @@ type ( func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) { loger := env.Loger.NewGroup(`AuthHandler`) - // ApiKey 请求头验证 - if env.Config.Auth.ApiKey_Enable { - loger.Debug(`ApiKeyAuth Enabled`) - out = append(out, func(c *gin.Context) { - resp.Wrap(c, func() *resp.Resp { - if auth := c.Request.Header.Get(`X-LxM-Auth`); auth != env.Config.Auth.ApiKey_Value { - loger.Debug(`验证失败: %q`, auth) - return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`} - } - return nil - }) - }) - } // RateLimit 速率限制 /* 逻辑: @@ -51,9 +38,16 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) { block_mem := int(env.Config.Auth.RateLimit_Block * env.Config.Auth.RateLimit_BanNum) bannum := env.Config.Auth.RateLimit_Single + env.Config.Auth.RateLimit_BanNum bantim := int64(env.Config.Auth.RateLimit_BanTim) + var getIp func(c *gin.Context) string + if env.Config.Main.NgProxy { + loger.Info(`已开启反向代理兼容模式`) + getIp = func(c *gin.Context) string { return c.ClientIP() } + } else { + getIp = func(c *gin.Context) string { return c.RemoteIP() } + } out = append(out, func(c *gin.Context) { resp.Wrap(c, func() *resp.Resp { - rip := c.RemoteIP() + rip := getIp(c) if rip == `` { rip = `0.0.0.0` } @@ -84,21 +78,18 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) { }) }) } + // ApiKey 请求头验证 + if env.Config.Auth.ApiKey_Enable { + loger.Debug(`ApiKeyAuth Enabled`) + out = append(out, func(c *gin.Context) { + resp.Wrap(c, func() *resp.Resp { + if auth := c.Request.Header.Get(`X-LxM-Auth`); auth != env.Config.Auth.ApiKey_Value { + loger.Debug(`验证失败: %q`, auth) + return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`} + } + return nil + }) + }) + } return append(out, h) } - -// 请求验证 -// func AuthHandler(c *gin.Context) { -// loger := env.Loger.NewGroup(`AuthHandler`) -// resp.Wrap(c, func() *resp.Resp { -// // ApiKey -// if env.Config.Auth.ApiKey_Enable { -// if auth := c.Request.Header.Get(`X-LxM-Auth`); auth != env.Config.Auth.ApiKey_Value { -// loger.Debug(`验证失败: %q`, auth) -// return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`} -// } -// } -// return nil -// }) -// // c.Next() -// } diff --git a/src/middleware/resp/resp.go b/src/middleware/resp/resp.go index a441f86..ac237f9 100644 --- a/src/middleware/resp/resp.go +++ b/src/middleware/resp/resp.go @@ -36,7 +36,7 @@ var statusMap = map[int]int{ 6: http.StatusBadRequest, // 参数错误 } -var ErrMp3 = `https://r2eu.zw-cdn.tk/gh/lx-source/static/error.mp3` +var ErrMp3 = `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3` // 返回请求 /* diff --git a/src/middleware/util/util.go b/src/middleware/util/util.go index 2ac49fa..85b334e 100644 --- a/src/middleware/util/util.go +++ b/src/middleware/util/util.go @@ -1,8 +1,21 @@ package util -import "github.com/gin-gonic/gin" +import ( + "lx-source/src/env" + "net/http" + + "github.com/ZxwyWebSite/ztool" + "github.com/gin-gonic/gin" +) // 将路由参数转为Map +/* + `:s/:id/:q` -> { + `s`: `source`, + `id`: `musicId`, + `q`: `quality`, + } +*/ func ParaMap(c *gin.Context) map[string]string { parmlen := len(c.Params) parms := make(map[string]string, parmlen) @@ -11,3 +24,28 @@ func ParaMap(c *gin.Context) map[string]string { } return parms } + +var pathCache string + +func init() { + env.Inits.Add(func() { + if !env.Config.Main.NgProxy { + pathCache = `/` + } + }) +} + +// 动态获取相对路径 <路径> +/* + HOST = `192.168.10.22:1011` + URI = `/path/to/lxs/link/wy/2049512697/flac` + sub = `link/` + -> http://192.168.10.22:1011/path/to/lxs/ +*/ +func GetPath(c *http.Request, sub string) string { + // 从缓存读取相对路径 `/path/to/lxs/` or `/` + if pathCache == `` { + pathCache = ztool.Str_Before(c.RequestURI, sub) + } + return ztool.Str_FastConcat(`http://`, c.Host, pathCache) +} diff --git a/src/server/app_lxmusic.go b/src/server/app_lxmusic.go new file mode 100644 index 0000000..d9de46c --- /dev/null +++ b/src/server/app_lxmusic.go @@ -0,0 +1,59 @@ +package server + +import ( + "lx-source/src/middleware/resp" + + "github.com/ZxwyWebSite/ztool" + "github.com/gin-gonic/gin" +) + +type queryObj struct { + Name string `json:"name"` // 歌名 + Singer string `json:"singer"` // 歌手 + Source string `json:"source"` // 平台 + Songmid string `json:"songmid"` // 音乐ID + Interval string `json:"interval"` // 时长 + AlbumName string `json:"albumName"` // 专辑 + Img string `json:"img"` // 封面 + // TypeURL struct { + // } `json:"typeUrl"` // 未知 + AlbumID string `json:"albumId"` // 专辑ID + // 支持音质 + Types []struct { + Type string `json:"type"` // 音质 + Size string `json:"size"` // 大小 + Hash string `json:"hash"` // 哈希(kg only) + } `json:"types"` + // tx + StrMediaMid string `json:"strMediaMid"` // 当前文件ID + AlbumMid string `json:"albumMid"` // 专辑ID + SongID int `json:"songId"` // 音乐ID + // mg + CopyrightID string `json:"copyrightId"` // 音乐ID + LrcURL string `json:"lrcUrl"` // lrc歌词 + MrcURL string `json:"mrcUrl"` // mrc歌词 + TrcURL string `json:"trcUrl"` // trc歌词 + // kg + Hash string `json:"hash"` // 文件哈希(kg only) +} + +func loadLxMusic(lx *gin.RouterGroup) { + // 获取链接 + lx.POST(`/link/:q`, func(c *gin.Context) { + resp.Wrap(c, func() *resp.Resp { + var obj queryObj + if err := c.ShouldBindJSON(&obj); err != nil { + return &resp.Resp{Code: 6, Msg: `解析错误: ` + err.Error()} + } + pams := map[string]string{ + `s`: obj.Source, + `id`: ztool.Str_Select(obj.Hash, obj.CopyrightID, obj.Songmid), + } + for k, v := range pams { + c.Params = append(c.Params, gin.Param{Key: k, Value: v}) + } + return nil + }) + }, linkHandler) + // lx.GET(`/info`) +} diff --git a/src/server/app_musicfree.go b/src/server/app_musicfree.go new file mode 100644 index 0000000..972a90c --- /dev/null +++ b/src/server/app_musicfree.go @@ -0,0 +1,34 @@ +package server + +import ( + "lx-source/src/middleware/util" + "lx-source/src/sources" + "net/http" + + "github.com/ZxwyWebSite/ztool" + "github.com/gin-gonic/gin" +) + +func loadMusicFree(mf *gin.RouterGroup) { + // 插件订阅 + mf.GET(`/subscribe`, func(c *gin.Context) { + slist := []string{sources.S_wy, sources.S_mg, sources.S_kw, sources.S_kg, sources.S_tx, sources.S_lx} + type plugins struct { + Name string `json:"name"` + Url string `json:"url"` + Version string `json:"version"` + } + length := len(slist) + plgs := make([]plugins, length) + url := ztool.Str_FastConcat(util.GetPath(c.Request, `app/`), `public/musicfree/`) + for i := 0; i < length; i++ { + name := `lxs-` + slist[i] + plgs[i] = plugins{ + Name: name, + Url: ztool.Str_FastConcat(url, name, `.js`), + Version: `0.0.0`, + } + } + c.JSON(http.StatusOK, gin.H{`plugins`: plgs}) + }) +} diff --git a/src/middleware/loadpublic/loadpublic.go b/src/server/loadpublic.go similarity index 63% rename from src/middleware/loadpublic/loadpublic.go rename to src/server/loadpublic.go index d63cd6d..9b181ae 100644 --- a/src/middleware/loadpublic/loadpublic.go +++ b/src/server/loadpublic.go @@ -1,7 +1,8 @@ // 静态资源 -package loadpublic +package server import ( + "bytes" "embed" "fmt" "io" @@ -11,14 +12,16 @@ import ( "path/filepath" "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/x/bytesconv" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/render" ) //go:embed public -var publicEM embed.FS // 打包默认Public目录 src/router/router.go +var publicEM embed.FS // 打包默认Public目录 src/server/public // 载入Public目录并设置路由 -func LoadPublic(r *gin.Engine) { +func loadPublic(r *gin.Engine) { pf := env.Loger.NewGroup(`PublicFS`) dir := ztool.Str_FastConcat(env.RunPath, `/data/public`) publicFS, err := fs.Sub(publicEM, `public`) @@ -57,6 +60,8 @@ func LoadPublic(r *gin.Engine) { pf.Info(`全部静态资源导出完成, 祝你使用愉快 ^_^`) } } + pf.Free() + // 使用本地public目录 // httpFS = gin.Dir(dir, false) // r.GET(`/:file`, func(c *gin.Context) { // file := c.Param(`file`) @@ -69,7 +74,32 @@ func LoadPublic(r *gin.Engine) { // c.FileFromFS(file, httpFS) // } // }) - r.StaticFileFS(`/favicon.ico`, `icon.ico`, httpFS) - r.StaticFileFS(`/lx-custom-source.js`, `lx-custom-source.js`, httpFS) + // 自动填写源脚本参数 + if env.Config.Script.Auto > 0 { + file, _ := publicFS.Open(`lx-custom-source.js`) + data, _ := io.ReadAll(file) + file.Close() + data = bytes.Replace(data, + bytesconv.StringToBytes(`http://127.0.0.1:1011/`), + bytesconv.StringToBytes(env.Config.Cache.Local_Bind), 1, + ) + if env.Config.Auth.ApiKey_Enable && env.Config.Script.Auto >= 2 { + data = bytes.Replace(data, + bytesconv.StringToBytes(`apipass = ''`), + bytesconv.StringToBytes(ztool.Str_FastConcat( + `apipass = '`, env.Config.Auth.ApiKey_Value, `'`, + )), 1, + ) + } + r.GET(`/lx-custom-source.js`, func(c *gin.Context) { + c.Render(http.StatusOK, render.Data{ + ContentType: `text/javascript; charset=utf-8`, + Data: data, + }) + }) + } else { + r.StaticFileFS(`/lx-custom-source.js`, `lx-custom-source.js`, httpFS) + } + r.StaticFileFS(`/favicon.ico`, `lx-icon.ico`, httpFS) r.StaticFS(`/public`, httpFS) } diff --git a/src/server/loadquality.go b/src/server/loadquality.go new file mode 100644 index 0000000..42db816 --- /dev/null +++ b/src/server/loadquality.go @@ -0,0 +1,56 @@ +package server + +import "lx-source/src/env" + +var ( + // 默认音质 + defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`} + // 试听音质 + tstQuality = []string{`128k`} + // 标准音质 + stdQuality = []string{`128k`, `320k`, `flac`} +) + +// 自动生成支持的音质表 +func loadQMap() [][]string { + m := make([][]string, 6) + // 0.wy + if env.Config.Source.Enable_Wy { + if env.Config.Custom.Wy_Enable { + m[0] = defQuality + } else { + m[0] = tstQuality + } + } + // 1.mg + if env.Config.Source.Enable_Mg { + if env.Config.Custom.Mg_Enable { + m[1] = defQuality + } + } + // 2.kw + if env.Config.Source.Enable_Kw { + if env.Config.Custom.Kw_Enable { + m[2] = stdQuality + } + } + // 3.kg + if env.Config.Source.Enable_Kg { + if env.Config.Custom.Kg_Enable { + m[3] = tstQuality + } + } + // 4.tx + if env.Config.Source.Enable_Tx { + if env.Config.Custom.Tx_Enable { + m[4] = stdQuality + } else { + m[4] = tstQuality + } + } + // 5.lx + // if env.Config.Source.Enable_Lx { + // m[5] = defQuality + // } + return m +} diff --git a/src/middleware/loadpublic/public/icon.ico b/src/server/public/icon.ico similarity index 100% rename from src/middleware/loadpublic/public/icon.ico rename to src/server/public/icon.ico diff --git a/src/middleware/loadpublic/public/lx-custom-source.js b/src/server/public/lx-custom-source.js similarity index 87% rename from src/middleware/loadpublic/public/lx-custom-source.js rename to src/server/public/lx-custom-source.js index 66fdeaf..e34533a 100644 --- a/src/middleware/loadpublic/public/lx-custom-source.js +++ b/src/server/public/lx-custom-source.js @@ -3,23 +3,19 @@ * @description Client * version 1.0.1 * @author Zxwy - * @homepage https://github.com/ZxwyWebSite/lx-source + * homepage null */ // 脚本配置 const version = '1.0.3' // 脚本版本 const apiaddr = 'http://127.0.0.1:1011/' // 服务端地址,末尾加斜杠 -const apipass = '' // 验证密钥,由服务端自动生成 '${apipass}' +const apipass = '' // 验证密钥,填在单引号内 const devmode = true // 调试模式 +// const timeout = 60 * 1000 // 请求超时(ms) // 常量 & 默认值 const { EVENT_NAMES, request, on, send } = window.lx ?? globalThis.lx const defs = { type: 'music', actions: ['musicUrl'] } -// const defaults = { -// type: 'music', // 目前固定为 music -// actions: ['musicUrl'], // 目前固定为 ['musicUrl'] -// qualitys: ['128k', '320k', 'flac', 'flac24bit'], // 当前脚本的该源所支持获取的Url音质,有效的值有:['128k', '320k', 'flac', 'flac24bit'] -// } const defheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 HBPC/12.1.2.300', 'Accept': 'application/json, text/plain, */*', @@ -39,6 +35,7 @@ const conf = { const httpRequest = (url, options) => new Promise((resolve, reject) => { options.headers = { ...defheaders, ...options.headers } // 添加默认请求头 + // options.timeout ? options.timeout : timeout // 添加默认请求超时 request(url, options, (err, resp) => { if (err) return reject(err) resolve(resp.body) @@ -80,8 +77,7 @@ const init = () => { 'use strict'; console.log('初始化脚本, 版本: %s, 服务端地址: %s', version, apiaddr) var stat = false; var msg = ''; var updUrl = ''; var sourcess = {} - httpRequest(apiaddr, { method: 'get' }) - .catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) }) + httpRequest(apiaddr, { method: 'get', timeout: 1000 * 10 }) .then((body) => { if (!body) { msg = '初始化失败:' + '无返回数据'; return } console.log('获取服务端数据成功: %o, 版本: %s', body, body.version) @@ -104,7 +100,7 @@ const init = () => { if (source[v] != null /*== true*/) { sourcess[v] = { name: v, - ...defs, // ...defaults, + ...defs, qualitys: source[v], // 支持返回音质时启用 使用后端音质表 } } @@ -112,18 +108,13 @@ const init = () => { // 完成初始化 stat = true }) + .catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) }) .finally(() => { // 脚本初始化完成后需要发送inited事件告知应用 send(EVENT_NAMES.inited, { status: stat, // 初始化成功 or 失败 (初始化失败不打开控制台, 使用更新提示接口返回信息) openDevTools: stat ? devmode : false, // 是否打开开发者工具,方便用于调试脚本 'devmode' or 'stat ? devmode : false' sources: sourcess, // 使用服务端源列表 - // sources: { // 当前脚本支持的源 - // wy: { name: '网易音乐', ...defaults, }, - // mg: { name: '咪咕音乐', ...defaults, }, - // kw: { name: '酷我音乐', ...defaults, }, - // // kg: { name: '酷狗音乐', ...defaults, }, // 暂不支持,仅供换源 - // }, }) // 发送更新提示 if (msg) send(EVENT_NAMES.updateAlert, { log: '提示:' + msg, updateUrl: updUrl ? apiaddr + updUrl : '' }) diff --git a/src/server/public/lx-icon.ico b/src/server/public/lx-icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2d7258f1d2902f623f5d2f5fa12cecc90b31ada1 GIT binary patch literal 165662 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 literal 0 HcmV?d00001 diff --git a/src/middleware/loadpublic/public/test.txt b/src/server/public/test.txt similarity index 100% rename from src/middleware/loadpublic/public/test.txt rename to src/server/public/test.txt diff --git a/src/router/router.go b/src/server/router.go similarity index 81% rename from src/router/router.go rename to src/server/router.go index bbc1f39..b42234c 100644 --- a/src/router/router.go +++ b/src/server/router.go @@ -1,11 +1,10 @@ -package router +package server import ( "lx-source/src/caches" "lx-source/src/env" "lx-source/src/middleware/auth" "lx-source/src/middleware/dynlink" - "lx-source/src/middleware/loadpublic" "lx-source/src/middleware/resp" "lx-source/src/middleware/util" "lx-source/src/sources" @@ -16,41 +15,6 @@ import ( "github.com/gin-gonic/gin" ) -var ( - // 默认音质 - defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`} - // 试听音质 - tstQuality = []string{`128k`} - // 标准音质 - stdQuality = []string{`128k`, `320k`, `flac`} -) - -// 自动生成支持的音质表 -func loadQMap() [][]string { - m := make([][]string, 6) - // 0.wy - if env.Config.Custom.Wy_Enable { - m[0] = defQuality - } else { - m[0] = tstQuality - } - // 1.mg - m[1] = defQuality - // 2.kw - m[2] = defQuality - // 3.kg - m[3] = tstQuality - // 4.tx - if env.Config.Custom.Tx_Enable { - m[4] = stdQuality - } else { - m[4] = tstQuality - } - // 5.lx - // m[sources.S_lx] = defQuality - return m -} - // 载入路由 func InitRouter() *gin.Engine { r := gin.Default() @@ -70,14 +34,11 @@ func InitRouter() *gin.Engine { // `github`: `https://github.com/ZxwyWebSite/lx-source`, // 可用平台 `source`: gin.H{ - sources.S_wy: qmap[0], //true, - sources.S_mg: qmap[1], //true, - sources.S_kw: qmap[2], //true, - sources.S_kg: qmap[3], //[]string{`128k`, `320k`}, // 测试结构2, 启用时返回音质列表, 禁用为false - sources.S_tx: qmap[4], //gin.H{ // "测试结构 不代表最终方式" - // `enable`: false, - // `qualitys`: []string{`128k`, `320k`, `flac`, `flac24bit`}, - // }, + sources.S_wy: qmap[0], + sources.S_mg: qmap[1], + sources.S_kw: qmap[2], + sources.S_kg: qmap[3], + sources.S_tx: qmap[4], sources.S_lx: qmap[5], }, // 自定义源脚本更新 @@ -85,12 +46,13 @@ func InitRouter() *gin.Engine { }) }) // 静态文件 - loadpublic.LoadPublic(r) + loadPublic(r) // r.StaticFile(`/favicon.ico`, `public/icon.ico`) // r.StaticFile(`/lx-custom-source.js`, `public/lx-custom-source.js`) // 解析接口 r.GET(`/link/:s/:id/:q`, auth.InitHandler(linkHandler)...) dynlink.LoadHandler(r) + // 动态链? // r.GET(`/file/:t/:x/:f`, dynlink.FileHandler()) // if cache, ok := caches.UseCache.(*localcache.Cache); ok { // r.Static(`/file`, cache.Path) @@ -98,10 +60,16 @@ func InitRouter() *gin.Engine { // if env.Config.Cache.Mode == `local` { // r.Static(`/file`, env.Config.Cache.Local_Path) // } - // 软件接口 + // 功能接口 // api := r.Group(`/api`) // { - // api.GET(`/lx`, lxHandler) // 洛雪音乐 + // api.GET(`/:s/:m/:q`) // {source}/{method}/{query} + // } + // 软件接口 + // app := r.Group(`/app`) + // { + // loadLxMusic(app.Group(`/lxmusic`)) + // loadMusicFree(app.Group(`/musicfree`)) // } // 数据接口 // r.GET(`/file/:t/:hq/:n`, func(c *gin.Context) { @@ -136,11 +104,12 @@ func linkHandler(c *gin.Context) { s := parms[`s`] //c.Param(`s`) //getParam(`s`) // source 平台 wy, mg, kw id := parms[`id`] //c.Param(`id`) //getParam(`id`) // sid 音乐ID wy: songmid, mg: copyrightId q := parms[`q`] //c.Param(`q`) //getParam(`q`) // quality 音质 128k / 320k / flac / flac24bit - env.Loger.NewGroup(`LinkQuery`).Debug(`s: %v, id: %v, q: %v`, s, id, q) + env.Loger.NewGroup(`LinkQuery`).Debug(`s: %v, id: %v, q: %v`, s, id, q).Free() if ztool.Chk_IsNilStr(s, q, id) { return &resp.Resp{Code: 6, Msg: `参数不全`} // http.StatusBadRequest } cquery := caches.NewQuery(s, id, q) + cquery.Request = c.Request // fmt.Printf("%+v\n", cquery) defer cquery.Free() // _, ok := sources.UseSource.Verify(cquery) // 获取请求音质 同时检测是否支持(如kw源没有flac24bit) qualitys[q][s]rquery @@ -152,7 +121,7 @@ func linkHandler(c *gin.Context) { clink, ok := env.Cache.Get(cquery.Query()) if ok { if str, ok := clink.(string); ok { - env.Loger.NewGroup(`MemCache`).Debug(`MemHIT [%q]=>[%q]`, cquery.Query(), str) + env.Loger.NewGroup(`MemCache`).Debug(`MemHIT [%q]=>[%q]`, cquery.Query(), str).Free() if str == `` { return &resp.Resp{Code: 2, Msg: memRej} // 拒绝请求,当前一段时间内解析出错 `MemCache Reject` } @@ -165,6 +134,7 @@ func linkHandler(c *gin.Context) { cstat = caches.UseCache.Stat() } sc := env.Loger.NewGroup(`StatCache`) + defer sc.Free() if cstat { sc.Debug(`Method: Get, Query: %v`, cquery.Query()) if link := caches.UseCache.Get(cquery); link != `` { diff --git a/src/sources/builtin/driver.go b/src/sources/builtin/driver.go index 2b4d088..e08c716 100644 --- a/src/sources/builtin/driver.go +++ b/src/sources/builtin/driver.go @@ -7,6 +7,7 @@ import ( "lx-source/src/sources" "lx-source/src/sources/custom/kw" "lx-source/src/sources/custom/tx" + "lx-source/src/sources/custom/wy" "net/http" "strconv" "sync" @@ -50,12 +51,22 @@ func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) { } // var outlink string jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`) + defer jx.Free() switch c.Source { case sources.S_wy: if !env.Config.Custom.Wy_Enable { msg = sources.ErrDisable return } + if wy.Url != nil { + ourl, emsg := wy.Url(c.MusicID, c.Quality) + if emsg != `` { + msg = emsg + return + } + outlink = ourl + break + } // 可用性验证 if env.Config.Source.MusicIdVerify { vef := wv_pool.Get().(*WyApi_Vef) @@ -120,6 +131,10 @@ func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) { // jx.Info(`WyLink, RealQuality: %v`, data.Level) outlink = data.URL case sources.S_mg: + if !env.Config.Custom.Mg_Enable { + msg = sources.ErrDisable + return + } resp := mg_pool.Get().(*MgApi_Song) defer mg_pool.Put(resp) @@ -134,9 +149,9 @@ func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) { jx.Debug(`Mg, Resp: %+v`, resp) if link := resp.Data.PlayURL; link != `` { outlink = `https:` + link - } // else { - // jx.Debug(`Mg, Err: %#v`, resp) - // } + } else { + msg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg) + } case sources.S_kw: if !env.Config.Custom.Kw_Enable { msg = sources.ErrDisable @@ -149,6 +164,10 @@ func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) { } outlink = ourl case sources.S_kg: + if !env.Config.Custom.Kg_Enable { + msg = sources.ErrDisable + return + } resp := kg_pool.Get().(*KgApi_Song) defer kg_pool.Put(resp) diff --git a/src/sources/builtin/types.go b/src/sources/builtin/types.go index a9a84f2..5f02d53 100644 --- a/src/sources/builtin/types.go +++ b/src/sources/builtin/types.go @@ -1,9 +1,11 @@ package builtin import ( + "encoding/base64" "lx-source/src/sources" "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/zcypt" ) type ( @@ -288,6 +290,7 @@ func init() { Header_Wy: &header_wy, Header_Mg: &header_mg, } - data := []byte{0x4a, 0x7f, 0x3, 0x1, 0x2, 0xff, 0x80, 0x0, 0x1, 0x5, 0x1, 0x6, 0x41, 0x70, 0x69, 0x5f, 0x57, 0x79, 0x1, 0xc, 0x0, 0x1, 0x6, 0x41, 0x70, 0x69, 0x5f, 0x4d, 0x67, 0x1, 0xc, 0x0, 0x1, 0x6, 0x56, 0x65, 0x66, 0x5f, 0x57, 0x79, 0x1, 0xc, 0x0, 0x1, 0x9, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x57, 0x79, 0x1, 0xff, 0x82, 0x0, 0x1, 0x9, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x4d, 0x67, 0x1, 0xff, 0x82, 0x0, 0x0, 0x0, 0x21, 0xff, 0x81, 0x4, 0x1, 0x1, 0x11, 0x6d, 0x61, 0x70, 0x5b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1, 0xff, 0x82, 0x0, 0x1, 0xc, 0x1, 0xc, 0x0, 0x0, 0xfe, 0x1, 0xca, 0xff, 0x80, 0x1, 0x23, 0x70, 0x74, 0x2e, 0x73, 0x61, 0x79, 0x71, 0x7a, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x3f, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x61, 0x70, 0x69, 0x53, 0x6f, 0x6e, 0x67, 0x55, 0x72, 0x6c, 0x56, 0x31, 0x1, 0x36, 0x6d, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x2e, 0x6d, 0x69, 0x67, 0x75, 0x2e, 0x63, 0x6e, 0x2f, 0x6d, 0x69, 0x67, 0x75, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x2f, 0x68, 0x35, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x67, 0x65, 0x74, 0x53, 0x6f, 0x6e, 0x67, 0x50, 0x6c, 0x61, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x1, 0x24, 0x70, 0x74, 0x2e, 0x73, 0x61, 0x79, 0x71, 0x7a, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x3f, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x73, 0x6d, 0x43, 0x68, 0x65, 0x61, 0x6b, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x1, 0x3, 0xa, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x5e, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2f, 0x35, 0x2e, 0x30, 0x20, 0x28, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x4e, 0x54, 0x20, 0x36, 0x2e, 0x31, 0x3b, 0x20, 0x57, 0x4f, 0x57, 0x36, 0x34, 0x29, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x57, 0x65, 0x62, 0x4b, 0x69, 0x74, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36, 0x20, 0x28, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x2c, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x29, 0x20, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x35, 0x30, 0x2e, 0x30, 0x2e, 0x32, 0x36, 0x36, 0x31, 0x2e, 0x38, 0x37, 0x10, 0x58, 0x2d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, 0x68, 0xe, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 0x15, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70, 0x74, 0x2e, 0x73, 0x61, 0x79, 0x71, 0x7a, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x1, 0x4, 0x6, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x38, 0x53, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x3d, 0x5a, 0x54, 0x49, 0x77, 0x4f, 0x44, 0x6b, 0x79, 0x4d, 0x44, 0x51, 0x74, 0x4f, 0x54, 0x45, 0x31, 0x4e, 0x53, 0x30, 0x30, 0x4d, 0x44, 0x68, 0x6c, 0x4c, 0x54, 0x68, 0x68, 0x4d, 0x57, 0x45, 0x74, 0x4d, 0x6a, 0x51, 0x30, 0x4e, 0x32, 0x59, 0x32, 0x4d, 0x7a, 0x6b, 0x32, 0x4f, 0x54, 0x41, 0x7a, 0x7, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6d, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x2e, 0x6d, 0x69, 0x67, 0x75, 0x2e, 0x63, 0x6e, 0x2f, 0x76, 0x34, 0x2f, 0x2, 0x42, 0x79, 0x20, 0x30, 0x34, 0x66, 0x38, 0x31, 0x34, 0x36, 0x31, 0x61, 0x39, 0x38, 0x63, 0x37, 0x61, 0x66, 0x35, 0x35, 0x37, 0x66, 0x65, 0x61, 0x33, 0x63, 0x66, 0x32, 0x38, 0x63, 0x34, 0x65, 0x61, 0x31, 0x35, 0x7, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x7, 0x30, 0x31, 0x34, 0x30, 0x30, 0x30, 0x44, 0x0} - ztool.Val_GobDecode(data, &initdata) + data := []byte{0x53, 0x6e, 0x38, 0x44, 0x41, 0x51, 0x4c, 0x2f, 0x67, 0x41, 0x41, 0x42, 0x42, 0x51, 0x45, 0x47, 0x51, 0x58, 0x42, 0x70, 0x58, 0x31, 0x64, 0x35, 0x41, 0x51, 0x77, 0x41, 0x41, 0x51, 0x5a, 0x42, 0x63, 0x47, 0x6c, 0x66, 0x54, 0x57, 0x63, 0x42, 0x44, 0x41, 0x41, 0x42, 0x42, 0x6c, 0x5a, 0x6c, 0x5a, 0x6c, 0x39, 0x58, 0x65, 0x51, 0x45, 0x4d, 0x41, 0x41, 0x45, 0x4a, 0x53, 0x47, 0x56, 0x68, 0x5a, 0x47, 0x56, 0x79, 0x58, 0x31, 0x64, 0x35, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x45, 0x4a, 0x53, 0x47, 0x56, 0x68, 0x5a, 0x47, 0x56, 0x79, 0x58, 0x30, 0x31, 0x6e, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x41, 0x41, 0x49, 0x66, 0x2b, 0x42, 0x42, 0x41, 0x45, 0x42, 0x45, 0x57, 0x31, 0x68, 0x63, 0x46, 0x74, 0x7a, 0x64, 0x48, 0x4a, 0x70, 0x62, 0x6d, 0x64, 0x64, 0x63, 0x33, 0x52, 0x79, 0x61, 0x57, 0x35, 0x6e, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x45, 0x4d, 0x41, 0x51, 0x77, 0x41, 0x41, 0x50, 0x34, 0x42, 0x72, 0x76, 0x2b, 0x41, 0x41, 0x53, 0x52, 0x6a, 0x63, 0x32, 0x30, 0x75, 0x63, 0x32, 0x46, 0x35, 0x63, 0x58, 0x6f, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x32, 0x46, 0x77, 0x61, 0x53, 0x38, 0x2f, 0x64, 0x48, 0x6c, 0x77, 0x5a, 0x54, 0x31, 0x68, 0x63, 0x47, 0x6c, 0x54, 0x62, 0x32, 0x35, 0x6e, 0x56, 0x58, 0x4a, 0x73, 0x56, 0x6a, 0x45, 0x42, 0x4e, 0x6d, 0x30, 0x75, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x75, 0x62, 0x57, 0x6c, 0x6e, 0x64, 0x53, 0x35, 0x6a, 0x62, 0x69, 0x39, 0x74, 0x61, 0x57, 0x64, 0x31, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x76, 0x61, 0x44, 0x55, 0x76, 0x63, 0x47, 0x78, 0x68, 0x65, 0x53, 0x39, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x4c, 0x32, 0x64, 0x6c, 0x64, 0x46, 0x4e, 0x76, 0x62, 0x6d, 0x64, 0x51, 0x62, 0x47, 0x46, 0x35, 0x53, 0x57, 0x35, 0x6d, 0x62, 0x77, 0x45, 0x6c, 0x59, 0x33, 0x4e, 0x74, 0x4c, 0x6e, 0x4e, 0x68, 0x65, 0x58, 0x46, 0x36, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, 0x39, 0x68, 0x63, 0x47, 0x6b, 0x76, 0x50, 0x33, 0x52, 0x35, 0x63, 0x47, 0x55, 0x39, 0x59, 0x33, 0x4e, 0x74, 0x51, 0x32, 0x68, 0x6c, 0x59, 0x57, 0x74, 0x4e, 0x64, 0x58, 0x4e, 0x70, 0x59, 0x77, 0x45, 0x43, 0x43, 0x6c, 0x56, 0x7a, 0x5a, 0x58, 0x49, 0x74, 0x51, 0x57, 0x64, 0x6c, 0x62, 0x6e, 0x52, 0x65, 0x54, 0x57, 0x39, 0x36, 0x61, 0x57, 0x78, 0x73, 0x59, 0x53, 0x38, 0x31, 0x4c, 0x6a, 0x41, 0x67, 0x4b, 0x46, 0x64, 0x70, 0x62, 0x6d, 0x52, 0x76, 0x64, 0x33, 0x4d, 0x67, 0x54, 0x6c, 0x51, 0x67, 0x4e, 0x69, 0x34, 0x78, 0x4f, 0x79, 0x42, 0x58, 0x54, 0x31, 0x63, 0x32, 0x4e, 0x43, 0x6b, 0x67, 0x51, 0x58, 0x42, 0x77, 0x62, 0x47, 0x56, 0x58, 0x5a, 0x57, 0x4a, 0x4c, 0x61, 0x58, 0x51, 0x76, 0x4e, 0x54, 0x4d, 0x33, 0x4c, 0x6a, 0x4d, 0x32, 0x49, 0x43, 0x68, 0x4c, 0x53, 0x46, 0x52, 0x4e, 0x54, 0x43, 0x77, 0x67, 0x62, 0x47, 0x6c, 0x72, 0x5a, 0x53, 0x42, 0x48, 0x5a, 0x57, 0x4e, 0x72, 0x62, 0x79, 0x6b, 0x67, 0x51, 0x32, 0x68, 0x79, 0x62, 0x32, 0x31, 0x6c, 0x4c, 0x7a, 0x55, 0x77, 0x4c, 0x6a, 0x41, 0x75, 0x4d, 0x6a, 0x59, 0x32, 0x4d, 0x53, 0x34, 0x34, 0x4e, 0x78, 0x42, 0x59, 0x4c, 0x56, 0x4a, 0x6c, 0x63, 0x58, 0x56, 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x5a, 0x43, 0x31, 0x58, 0x61, 0x58, 0x52, 0x6f, 0x44, 0x6c, 0x68, 0x4e, 0x54, 0x45, 0x68, 0x30, 0x64, 0x48, 0x42, 0x53, 0x5a, 0x58, 0x46, 0x31, 0x5a, 0x58, 0x4e, 0x30, 0x41, 0x51, 0x51, 0x48, 0x55, 0x6d, 0x56, 0x6d, 0x5a, 0x58, 0x4a, 0x6c, 0x63, 0x68, 0x74, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x63, 0x7a, 0x6f, 0x76, 0x4c, 0x32, 0x30, 0x75, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x75, 0x62, 0x57, 0x6c, 0x6e, 0x64, 0x53, 0x35, 0x6a, 0x62, 0x69, 0x39, 0x32, 0x4e, 0x43, 0x38, 0x43, 0x51, 0x6e, 0x6b, 0x67, 0x4d, 0x44, 0x52, 0x6d, 0x4f, 0x44, 0x45, 0x30, 0x4e, 0x6a, 0x46, 0x68, 0x4f, 0x54, 0x68, 0x6a, 0x4e, 0x32, 0x46, 0x6d, 0x4e, 0x54, 0x55, 0x33, 0x5a, 0x6d, 0x56, 0x68, 0x4d, 0x32, 0x4e, 0x6d, 0x4d, 0x6a, 0x68, 0x6a, 0x4e, 0x47, 0x56, 0x68, 0x4d, 0x54, 0x55, 0x48, 0x59, 0x32, 0x68, 0x68, 0x62, 0x6d, 0x35, 0x6c, 0x62, 0x41, 0x63, 0x77, 0x4d, 0x54, 0x51, 0x77, 0x4d, 0x44, 0x42, 0x45, 0x42, 0x6b, 0x4e, 0x76, 0x62, 0x32, 0x74, 0x70, 0x5a, 0x54, 0x68, 0x54, 0x52, 0x56, 0x4e, 0x54, 0x53, 0x55, 0x39, 0x4f, 0x50, 0x56, 0x70, 0x55, 0x53, 0x58, 0x64, 0x50, 0x52, 0x47, 0x74, 0x35, 0x54, 0x55, 0x52, 0x52, 0x64, 0x45, 0x39, 0x55, 0x52, 0x54, 0x46, 0x4f, 0x55, 0x7a, 0x41, 0x77, 0x54, 0x55, 0x52, 0x6f, 0x62, 0x45, 0x78, 0x55, 0x61, 0x47, 0x68, 0x4e, 0x56, 0x30, 0x56, 0x30, 0x54, 0x57, 0x70, 0x52, 0x4d, 0x45, 0x34, 0x79, 0x57, 0x54, 0x4a, 0x4e, 0x65, 0x6d, 0x73, 0x79, 0x54, 0x31, 0x52, 0x42, 0x65, 0x67, 0x41, 0x3d} + dec, _ := zcypt.Base64Decode(base64.StdEncoding, data) + ztool.Val_GobDecode(dec, &initdata) } diff --git a/src/sources/custom/kw/player.go b/src/sources/custom/kw/player.go index 4ab2366..05652a9 100644 --- a/src/sources/custom/kw/player.go +++ b/src/sources/custom/kw/player.go @@ -60,12 +60,13 @@ func init() { default: loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Kw_Mode`) } - loger = nil + 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 @@ -100,6 +101,7 @@ func bdapi(songMid, quality string) (ourl, msg string) { 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 diff --git a/src/sources/custom/tx/player.go b/src/sources/custom/tx/player.go index 9a8b00c..db87547 100644 --- a/src/sources/custom/tx/player.go +++ b/src/sources/custom/tx/player.go @@ -91,6 +91,7 @@ type playInfo struct { func Url(songMid, quality string) (ourl, msg string) { loger := env.Loger.NewGroup(`Tx`) + defer loger.Free() infoFile, ok := fileInfo[quality] if !ok || (!env.Config.Custom.Tx_Enable && quality != sources.Q_128k) { msg = sources.E_QNotSupport //`不支持的音质` diff --git a/src/sources/custom/wy/init.go b/src/sources/custom/wy/init.go index 940d233..d07a832 100644 --- a/src/sources/custom/wy/init.go +++ b/src/sources/custom/wy/init.go @@ -1,13 +1,14 @@ package wy -import ( - "lx-source/src/env" - "maps" - "time" +// import ( +// "lx-source/src/env" +// wy "lx-source/src/sources/custom/wy/modules" +// "maps" +// "time" - "github.com/ZxwyWebSite/ztool/logs" - "github.com/ZxwyWebSite/ztool/x/cookie" -) +// "github.com/ZxwyWebSite/ztool/logs" +// "github.com/ZxwyWebSite/ztool/x/cookie" +// ) /* 刷新登录模块 (来自 NeteaseCloudMusicApi) @@ -19,43 +20,43 @@ import ( 原代码未提供详细描述,无法确定有效结果判断条件,暂时先这么写 */ -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) - } - }) -} +// func init() { +// env.Inits.Add(func() { +// if env.Config.Custom.Wy_Refresh_Enable && env.Config.Custom.Wy_Api_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_Api_Cookie)) +// res, err := wy.LoginRefresh(wy.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_Api_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) +// } +// }) +// } diff --git a/src/sources/custom/wy/encrypt.go b/src/sources/custom/wy/modules/core_crypto.go similarity index 87% rename from src/sources/custom/wy/encrypt.go rename to src/sources/custom/wy/modules/core_crypto.go index 3076bf1..7116443 100644 --- a/src/sources/custom/wy/encrypt.go +++ b/src/sources/custom/wy/modules/core_crypto.go @@ -17,6 +17,8 @@ import ( "github.com/ZxwyWebSite/ztool/zcypt" ) +// crypto.js + var ( ivKey = bytesconv.StringToBytes(`0102030405060708`) presetKey = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`) @@ -28,21 +30,6 @@ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7cl eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`) ) -// func eapiEncrypt(url, text string) map[string][]string { -// digest := zcypt.CreateMD5(bytesconv.StringToBytes(ztool.Str_FastConcat( -// `nobody`, url, `use`, text, `md5forencrypt`, -// ))) -// data := ztool.Str_FastConcat( -// url, `-36cd479b6b5-`, text, `-36cd479b6b5-`, digest, -// ) -// // 注:JSON编码时会自动将[]byte转为string,这里省去一步转换 -// return map[string][]string{ -// `params`: {bytesconv.BytesToString(aesEncrypt(bytesconv.StringToBytes(data), eapiKey, false))}, -// } -// } - -// crypto.js - func aesEncrypt(text, key []byte, iv bool) []byte { pad := 16 - len(text)%16 text = append(text, bytes.Repeat([]byte{byte(pad)}, pad)...) diff --git a/src/sources/custom/wy/utils.go b/src/sources/custom/wy/modules/core_request.go similarity index 99% rename from src/sources/custom/wy/utils.go rename to src/sources/custom/wy/modules/core_request.go index ca6ca85..aafbaf1 100644 --- a/src/sources/custom/wy/utils.go +++ b/src/sources/custom/wy/modules/core_request.go @@ -222,9 +222,9 @@ func createRequest(method, url string, data map[string]any, options reqOptions) if err != nil { answer.Status = 502 answer.Body = map[string]any{`code`: 502, `msg`: err.Error()} - return err + // return err } - return nil + return err // nil }, }, ) diff --git a/src/sources/custom/wy/modules/core_types.go b/src/sources/custom/wy/modules/core_types.go new file mode 100644 index 0000000..5de7928 --- /dev/null +++ b/src/sources/custom/wy/modules/core_types.go @@ -0,0 +1,84 @@ +package wy + +type ( + // 音乐URL + 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"` + } + // 音乐是否可用 + VerifyInfo struct { + Code int16 `json:"code"` + Success bool `json:"success"` + Message string `json:"message"` + } + // 音质数据 + QualityData struct { + Br int `json:"br"` // 比特率 Bit Rate + Fid int `json:"fid"` // ? + Size int `json:"size"` // 文件大小 + Vd float64 `json:"vd"` // Volume Delta + Sr int `json:"sr"` // 采样率 Sample Rate + } + // 歌曲音质详情 + QualityDetail struct { + Data struct { + SongID int `json:"songId"` + H QualityData `json:"h"` // 高质量文件信息 + M QualityData `json:"m"` // 中质量文件信息 + L QualityData `json:"l"` // 低质量文件信息 + Sq QualityData `json:"sq"` // 无损质量文件信息 + Hr QualityData `json:"hr"` // Hi-Res质量文件信息 + Db QualityData `json:"db"` // 杜比音质 + Jm QualityData `json:"jm"` // jymaster(超清母带) + Je QualityData `json:"je"` // jyeffect(高清环绕声) + Sk QualityData `json:"sk"` // sky(沉浸环绕声) + } `json:"data"` + Code int `json:"code"` + Message string `json:"message"` + Success bool `json:"success"` + Error bool `json:"error"` + } +) diff --git a/src/sources/custom/wy/login_refresh.go b/src/sources/custom/wy/modules/login_refresh.go similarity index 100% rename from src/sources/custom/wy/login_refresh.go rename to src/sources/custom/wy/modules/login_refresh.go diff --git a/src/sources/custom/wy/song_url.go b/src/sources/custom/wy/modules/song_url.go similarity index 100% rename from src/sources/custom/wy/song_url.go rename to src/sources/custom/wy/modules/song_url.go diff --git a/src/sources/custom/wy/song_url_v1.go b/src/sources/custom/wy/modules/song_url_v1.go similarity index 100% rename from src/sources/custom/wy/song_url_v1.go rename to src/sources/custom/wy/modules/song_url_v1.go diff --git a/src/sources/custom/wy/player.go b/src/sources/custom/wy/player.go index 588061e..804d77f 100644 --- a/src/sources/custom/wy/player.go +++ b/src/sources/custom/wy/player.go @@ -4,73 +4,82 @@ import ( "lx-source/src/env" "lx-source/src/sources" "lx-source/src/sources/custom/utils" + wy "lx-source/src/sources/custom/wy/modules" + "net/http" + "sync" "github.com/ZxwyWebSite/ztool" "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"` +var ( + wy_pool = &sync.Pool{New: func() any { return new(wy.PlayInfo) }} + // wv_pool *sync.Pool + + Url func(string, string) (string, string) +) + +func init() { + env.Inits.Add(func() { + loger := env.Loger.NewGroup(`WyInit`) + switch env.Config.Custom.Wy_Mode { + case `0`, `builtin`: + loger.Debug(`use builtin`) + // if env.Config.Source.MusicIdVerify { + // wv_pool = &sync.Pool{New: func() any { return new(verifyInfo) }} + // } + // Url = builtin + case `1`, `163api`: + if env.Config.Custom.Wy_Api_Cookie == `` { + loger.Fatal(`使用163api且Cookie参数为空`) + } + switch env.Config.Custom.Wy_Api_Type { + case `0`, `native`: + loger.Debug(`use 163api module`) + Url = nmModule + case `1`, `remote`: + loger.Debug(`use 163api custom`) + if env.Config.Custom.Wy_Api_Address == `` { + loger.Fatal(`自定义接口地址为空`) + } + if env.Config.Custom.Wy_Api_Address[len(env.Config.Custom.Wy_Api_Address)-1] != '/' { + env.Config.Custom.Wy_Api_Address += "/" // 补全尾部斜杠 + } + loger.Info(`使用自定义接口: %v`, env.Config.Custom.Wy_Api_Address) + Url = nmCustom + default: + loger.Fatal(`未定义的调用方式,请检查配置 [Custom].Wy_Api_Type`) + } + default: + loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Wy_Mode`) + } + loger.Free() + }) } -func Url(songMid, quality string) (ourl, msg string) { +// func builtin(songMid, quality string) (ourl, msg string) { +// loger := env.Loger.NewGroup(`Wy`) +// defer loger.Free() +// return +// } + +func nmModule(songMid, quality string) (ourl, msg string) { loger := env.Loger.NewGroup(`Wy`) + defer loger.Free() rquality, ok := qualityMap[quality] if !ok { msg = sources.E_QNotSupport return } - cookies := cookie.Parse(env.Config.Custom.Wy_Cookie) - answer, err := SongUrlV1(ReqQuery{ + cookies := cookie.Parse(env.Config.Custom.Wy_Api_Cookie) + answer, err := wy.SongUrlV1(wy.ReqQuery{ Cookie: cookie.ToMap(cookies), Ids: songMid, // Br: rquality, Level: rquality, }) - var body playInfo + body := wy_pool.Get().(*wy.PlayInfo) + defer wy_pool.Put(body) if err == nil { err = ztool.Val_MapToStruct(answer.Body, &body) } @@ -98,56 +107,42 @@ func Url(songMid, quality string) (ourl, msg string) { return } -// func PyUrl(songMid, quality string) (ourl, msg string) { -// loger := env.Loger.NewGroup(`Wy`) -// rquality, ok := qualityMap[quality] -// if !ok { -// msg = sources.E_QNotSupport -// return -// } -// path := `/api/song/enhance/player/url/v1` -// requestUrl := `https://interface.music.163.com/eapi/song/enhance/player/url/v1` -// var body builtin.WyApi_Song -// text := ztool.Str_FastConcat( -// `{"encodeType":"flac","ids":["`, songMid, `"],"level":"`, rquality, `"}`, -// ) -// var form url.Values = eapiEncrypt(path, text) -// // form, err := json.Marshal(eapiEncrypt(path, text)) -// // if err == nil { -// err := ztool.Net_Request( -// http.MethodPost, requestUrl, -// strings.NewReader(form.Encode()), //bytes.NewReader(form), -// []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{ -// `Cookie`: env.Config.Custom.Wy_Cookie, -// })}, -// []ztool.Net_ResHandlerFunc{ -// func(res *http.Response) error { -// body, err := io.ReadAll(res.Body) -// if err != nil { -// return err -// } -// loger.Info(`%s`, body) -// return ztool.Err_EsContinue -// }, -// ztool.Net_ResToStruct(&body), -// }, -// ) -// // } -// if err != nil { -// loger.Error(`Request: %s`, err) -// msg = sources.ErrHttpReq -// return -// } -// loger.Debug(`Resp: %+v`, body) -// if len(body.Data) == 0 { -// msg = `No Data:无返回数据` -// return -// } -// data := body.Data[0] -// if data.Level != rquality { -// msg = sources.E_QNotMatch -// return -// } -// ourl = utils.DelQuery(data.URL) -// return -// } +func nmCustom(songMid, quality string) (ourl, msg string) { + loger := env.Loger.NewGroup(`Wy`) + defer loger.Free() + rquality, ok := qualityMap[quality] + if !ok { + msg = sources.E_QNotSupport + return + } + body := wy_pool.Get().(*wy.PlayInfo) + defer wy_pool.Put(body) + err := ztool.Net_Request( + http.MethodGet, + ztool.Str_FastConcat( + env.Config.Custom.Wy_Api_Address, `song/url/v1`, `?id=`, songMid, `&level=`, rquality, + // `×tamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10), + ), nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{ + `Cookie`: env.Config.Custom.Wy_Api_Cookie, + })}, + []ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&body)}, + ) + if err != nil { + loger.Error(`SongUrl: %s`, err) + msg = sources.ErrHttpReq + return + } + loger.Debug(`Resp: %+v`, body) + if len(body.Data) == 0 { + msg = `No Data:无返回数据` + return + } + data := body.Data[0] + if data.Level != rquality { + msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, data.Level) + return + } + ourl = utils.DelQuery(data.URL) + return +} diff --git a/src/sources/source.go b/src/sources/source.go index 72276f3..90fac71 100644 --- a/src/sources/source.go +++ b/src/sources/source.go @@ -57,10 +57,44 @@ func (*NullSource) GetLink(*caches.Query) (string, string) { return ``, `NullSou var UseSource Source = &NullSource{} // = &builtin.Source{} // 统一错误 -// type Error struct { -// msg string +// type ( +// ErrDef struct { +// Typ string +// Msg string +// } +// ErrQul struct { +// Need string +// Real string +// } +// ) + +// func (e *ErrDef) Error() string { +// return ztool.Str_FastConcat(e.Typ, `: `, e.Msg) +// } +// func (e *ErrQul) Error() string { +// return ztool.Str_FastConcat(`实际音质不匹配: `, e.Need, ` <= `, e.Real) // } -// func (e *Error) Error() string { -// return ztool.Str_FastConcat(e.msg) +// 验证失败(Verify Failed) +// func ErrVerify(msg string) error { +// return &ErrDef{ +// Typ: Err_Verify, +// Msg: msg, +// } +// } + +// 实际音质不匹配 +// func ErrQuality(need, real string) error { +// return &ErrQul{ +// Need: need, +// Real: real, +// } +// } + +// 无返回数据(No Data) +// func ErrNoData() error { +// return &ErrDef{ +// Typ: `No Data`, +// Msg: ``, +// } // } diff --git a/src/sources/types.go b/src/sources/types.go new file mode 100644 index 0000000..e072a40 --- /dev/null +++ b/src/sources/types.go @@ -0,0 +1,46 @@ +package sources + +// MusicFree 数据结构 +type ( + // 其他 + IExtra map[string]interface{} + // 音乐 + IMusicItem struct { + Artist string `json:"artist"` // 作者 + Title string `json:"title"` // 歌曲标题 + Duration int `json:"duration,omitempty"` // 时长(s) + Album string `json:"album,omitempty"` // 专辑名 + Artwork string `json:"artwork,omitempty"` // 专辑封面图 + Url string `json:"url,omitempty"` // 默认音源 + Lrc string `json:"lrc,omitempty"` // 歌词URL + RawLrc string `json:"rawLrc,omitempty"` // 歌词文本(lrc格式 带时间戳) + Other IExtra `json:"extra,omitempty"` // 其他 + } + // 歌单 + IMusicSheetItem struct { + Artwork string `json:"artwork,omitempty"` // 封面图 + Title string `json:"title"` // 标题 + Description string `json:"description,omitempty"` // 描述 + WorksNum int `json:"worksNum,omitempty"` // 作品总数 + PlayCount int `json:"playCount,omitempty"` // 播放次数 + MusicList []IMusicItem `json:"musicList,omitempty"` // 播放列表 + CreateAt int64 `json:"createAt,omitempty"` // 歌单创建日期 + Artist string `json:"artist,omitempty"` // 歌单作者 + Other IExtra `json:"extra,omitempty"` // 其他 + } + // 专辑 + IAlbumItem IMusicSheetItem + // 作者 + IArtistItem struct { + Platform string `json:"platform,omitempty"` // 插件名 + ID interface{} `json:"id"` // 唯一id + Name string `json:"name"` // 姓名 + Fans int `json:"fans,omitempty"` // 粉丝数 + Description string `json:"description,omitempty"` // 简介 + Avatar string `json:"avatar,omitempty"` // 头像 + WorksNum int `json:"worksNum,omitempty"` // 作品数目 + MusicList []IMusicItem `json:"musicList,omitempty"` // 作者的音乐列表 + AlbumList []IAlbumItem `json:"albumList,omitempty"` // 作者的专辑列表 + Other IExtra `json:"extra,omitempty"` // 其他 + } +) diff --git a/update.md b/update.md index b68daac..b93d0ac 100644 --- a/update.md +++ b/update.md @@ -1,5 +1,60 @@ ## Lx-Source/更新日志 +#### \# 2024-01-31 v1.0.2-b12-d3 (dev) ++ [重构请求处理逻辑]: + ``` + 首先拆分为三部分:Before(前置检测), Middle(获取数据), After(验证结果) + 可解决部分重复代码,但传参难以统一 + ``` ++ [缓存数据目录]: + ``` + 128k.mp3 320k.mp3 flac.flac fl64.flac lyric.lrc cover.jpg info.json + | 各音质文件缓存 | 歌词 | 封面 | 详情 | + ``` ++ [数据库表结构]: + ``` + 暂未确定 + ``` ++ 计划:引入SQLite支持,缓存歌曲详情 ++ 更新go.mod依赖 ++ 默认文件权限改为0777 ++ 屏蔽部分未实现功能配置项 + +#### \# 2024-01-29 v1.0.2-b12-d3 (dev) ++ 增加启动参数 `-p 0777` 设置默认文件权限 解决非root用户下的权限问题 (部分情况0666不管用) ++ **(注:为保证正确解析八进制数据,开头的"0"不能去掉!)** ++ 低调行事,删除源脚本内Github仓库地址 ++ 构建时修改版本号? ++ 本地缓存:下载出错后删除文件 ++ 更新wy内置源Api地址 ++ 优化配置文件保存逻辑 ++ 支持使用自定义NeteaseCloudMusicApi项目(beta) ++ (注:当前测试阶段,强行与旧逻辑兼容,可能无法达到最佳性能) ++ 计划:重构源请求逻辑,合并builtin与custom目录? ++ 支持wy源检测音质是否可用(需启用qualityMapReverse支持) ++ 计划:将参数传入Query,忽略实际音质不匹配强制缓存 + +#### \# 2024-01-27 v1.0.2-b12-d3 (dev) ++ wy源Cookie长期有效(一个月左右),且刷新逻辑未知,暂时禁用刷新登录功能 ++ 将router和middleware整合到server文件夹 ++ 自适应本地缓存绑定地址(beta),多端口建议开启,但缺少灵活性 ++ windows平台调用loger.Fatal后显示按任意键继续(优化防止闪退) + +#### \# 2024-01-26 v1.0.2-b12-d3 (dev) ++ 计划增加命令行模式配置账号登录(测试版) ++ 启动命令 `./lx-source-linux-amd64v2 -e menu` ++ 清理部分无用注释 + +#### \# 2024-01-24 v1.0.2-b12-d3 (dev) + ++ 模块化wyApi部分√ ++ \[Auth\] 将Key验证模块移至速率限制模块后 ++ 优化main.go,使用zcypt.RandomBytes函数 ++ 添加对象池,手动释放Loger,优化创建速度 ++ 添加多端口监听支持 ++ 支持配置全局代理、伪装ip ++ 支持使用163api模式获取歌曲 + #### \# 2024-01-21 v1.0.2-b12-d2 (dev) + 新版api结构设计(暂定) ```