2024-01-07 v1.0.2-b0.9

This commit is contained in:
ZxwyWebSite 2024-01-07 23:11:01 +08:00
parent b500fca79a
commit d086e2934b
13 changed files with 265 additions and 69 deletions

View File

@ -10,6 +10,7 @@ import (
"lx-source/src/router" "lx-source/src/router"
"lx-source/src/sources" "lx-source/src/sources"
"lx-source/src/sources/builtin" "lx-source/src/sources/builtin"
"lx-source/src/sources/custom/tx"
"math/rand" "math/rand"
"net/http" "net/http"
"os" "os"
@ -166,9 +167,13 @@ func main() {
ise.Error(`未定义的音乐源,请检查配置 [Source].Mode本次启动禁用内置源`) ise.Error(`未定义的音乐源,请检查配置 [Source].Mode本次启动禁用内置源`)
} }
// 启动Http服务 // 载入必要模块
env.Loger.NewGroup(`ServStart`).Info(`服务端启动, 监听地址 %s`, env.Config.Main.Listen) env.Loger.NewGroup(`ServStart`).Info(`服务端启动, 监听地址 %s`, env.Config.Main.Listen)
loadFileLoger() loadFileLoger()
tx.Init()
env.Defer.Add(env.Tasker.Run(env.Loger)) // wait
// 启动Http服务
r := router.InitRouter() //InitRouter() r := router.InitRouter() //InitRouter()
server := &http.Server{ server := &http.Server{
Addr: env.Config.Main.Listen, Addr: env.Config.Main.Listen,

33
src/env/env.go vendored
View File

@ -2,14 +2,17 @@
package env package env
import ( import (
"time"
"github.com/ZxwyWebSite/ztool" "github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/cache/memo" "github.com/ZxwyWebSite/ztool/cache/memo"
"github.com/ZxwyWebSite/ztool/conf" "github.com/ZxwyWebSite/ztool/conf"
"github.com/ZxwyWebSite/ztool/logs" "github.com/ZxwyWebSite/ztool/logs"
"github.com/ZxwyWebSite/ztool/task"
) )
const ( const (
Version = `1.0.2-b0.8` Version = `1.0.2-b0.9`
) )
var ( var (
@ -46,7 +49,8 @@ type (
RateLimit_Block uint32 `comment:"检测范围每分区为x秒"` // 每x秒一个分区 RateLimit_Block uint32 `comment:"检测范围每分区为x秒"` // 每x秒一个分区
RateLimit_Global uint32 `comment:"全局速率限制单位次每x秒(暂未开放)"` RateLimit_Global uint32 `comment:"全局速率限制单位次每x秒(暂未开放)"`
RateLimit_Single uint32 `comment:"单IP速率限制单位次每x秒"` RateLimit_Single uint32 `comment:"单IP速率限制单位次每x秒"`
// RateLimit_BanNum uint32 `commemt:"容忍限度超出限制N倍后封禁"` RateLimit_BanNum uint32 `comment:"容忍限度超出限制N次后封禁"`
RateLimit_BanTim uint32 `comment:"封禁后每次延长时间"`
// 黑白名单 // 黑白名单
BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"` BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"`
BanList_White []string `comment:"host白名单"` BanList_White []string `comment:"host白名单"`
@ -64,17 +68,20 @@ type (
// ...(待实现) // ...(待实现)
} // `comment:""` } // `comment:""`
Conf_Custom struct { Conf_Custom struct {
// wy (暂未实现)
Wy_Enable bool `comment:"是否开启小芸源"`
// Wy_Cookie string `comment:"账号cookie数据"`
// mg (暂未实现)
// Mg_Enable bool `comment:"是否开启小蜜源"`
// kg (暂未实现) // kg (暂未实现)
// Kg_Enable bool `comment:"是否开启小枸源"` // Kg_Enable bool `comment:"是否开启小枸源"`
// tx // tx
Tx_Enable bool `comment:"是否开启小秋源"` Tx_Enable bool `comment:"是否开启小秋源"`
Tx_Ukey string `comment:"Cookie中/客户端的请求体中的comm.authst"` Tx_Ukey string `comment:"Cookie中/客户端的请求体中的comm.authst"`
Tx_Uuin string `comment:"key对应的QQ号"` Tx_Uuin string `comment:"key对应的QQ号"`
// wy (暂未实现) // tx refresh_login
// Wy_Enable bool `comment:"是否开启小芸源"` Tx_Refresh_Enable bool `comment:"是否启动刷新登录"`
// Wy_Cookie string `comment:"账号cookie数据"` Tx_Refresh_Interval int64 `comment:"刷新间隔 (由程序维护,非必要无需修改)"`
// mg (暂未实现)
// Mg_Enable bool `comment:"是否开启小蜜源"`
} }
Conf_Script struct { Conf_Script struct {
Ver string `comment:"自定义脚本版本" json:"ver"` Ver string `comment:"自定义脚本版本" json:"ver"`
@ -111,7 +118,7 @@ var (
defCfg = Conf{ defCfg = Conf{
Main: Conf_Main{ Main: Conf_Main{
Debug: false, Debug: false,
Listen: `0.0.0.0:1011`, Listen: `127.0.0.1:1011`,
Gzip: false, Gzip: false,
LogPath: `/data/logfile.log`, LogPath: `/data/logfile.log`,
Print: true, Print: true,
@ -126,6 +133,8 @@ var (
RateLimit_Block: 30, RateLimit_Block: 30,
RateLimit_Global: 1, RateLimit_Global: 1,
RateLimit_Single: 15, RateLimit_Single: 15,
RateLimit_BanNum: 5,
RateLimit_BanTim: 10,
BanList_Mode: `off`, BanList_Mode: `off`,
BanList_White: []string{`127.0.0.1`}, BanList_White: []string{`127.0.0.1`},
}, },
@ -136,6 +145,12 @@ var (
Proxy_Enable: false, Proxy_Enable: false,
Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`, Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`,
}, },
Custom: Conf_Custom{
Wy_Enable: true,
Tx_Enable: false,
Tx_Refresh_Enable: false,
Tx_Refresh_Interval: 86000,
},
Script: Conf_Script{ Script: Conf_Script{
Log: `发布更新 (请删除旧源后重新导入)进行了部分优化修复了部分Bug`, // 更新日志 Log: `发布更新 (请删除旧源后重新导入)进行了部分优化修复了部分Bug`, // 更新日志
@ -166,6 +181,8 @@ var (
}) })
Defer = new(ztool.Err_DeferList) Defer = new(ztool.Err_DeferList)
Cache = memo.NewMemoStoreConf(Loger, 300) // 内存缓存 默认每5分钟进行一次GC //memo.NewMemoStore() Cache = memo.NewMemoStoreConf(Loger, 300) // 内存缓存 默认每5分钟进行一次GC //memo.NewMemoStore()
Tasker = task.New(time.Hour, 2) // 定时任务 (暂时没有什么快速任务,默认每小时检测一次)
) )
// func init() { // func init() {

View File

@ -12,7 +12,7 @@ import (
type ( type (
RateLimit struct { RateLimit struct {
Tim int64 // 创建时间 Tim int64 // 创建时间 (注原子操作64位数据需放在结构体第一位或保证8字节对齐否则不兼容32位平台 https://pkg.go.dev/sync/atomic#pkg-note-BUG)
Num uint32 // 请求次数 Num uint32 // 请求次数
} }
) )
@ -40,6 +40,7 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
// 判断ip是否在白名单内是则直接放行 // 判断ip是否在白名单内是则直接放行
判断请求数+1是否大于限制True: 429 请求过快请稍后重试 判断请求数+1是否大于限制True: 429 请求过快请稍后重试
// 判断是否超出容忍限度是则封禁ip (暂未实现) // 判断是否超出容忍限度是则封禁ip (暂未实现)
// 超过容忍限度每次请求增加一个Block的时间
继续执行后续Handler 继续执行后续Handler
*/ */
if env.Config.Auth.RateLimit_Enable { if env.Config.Auth.RateLimit_Enable {
@ -47,7 +48,9 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
loger.Info(`已启用速率限制,当前配置 %v/%v`, env.Config.Auth.RateLimit_Single, env.Config.Auth.RateLimit_Block) 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} } newRateLimit := func() *RateLimit { return &RateLimit{Tim: time.Now().Unix(), Num: 1} }
block_int64 := int64(env.Config.Auth.RateLimit_Block) block_int64 := int64(env.Config.Auth.RateLimit_Block)
block_int := int(env.Config.Auth.RateLimit_Block) 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)
out = append(out, func(c *gin.Context) { out = append(out, func(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp { resp.Wrap(c, func() *resp.Resp {
rip := c.RemoteIP() rip := c.RemoteIP()
@ -62,6 +65,9 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
if oip.Tim+block_int64 > time.Now().Unix() { if oip.Tim+block_int64 > time.Now().Unix() {
oi := atomic.AddUint32(&oip.Num, 1) oi := atomic.AddUint32(&oip.Num, 1)
if oi > env.Config.Auth.RateLimit_Single { if oi > env.Config.Auth.RateLimit_Single {
if oi > bannum {
atomic.AddInt64(&oip.Tim, bantim)
}
return &resp.Resp{Code: 5, Msg: `请求过快,请稍后重试`} return &resp.Resp{Code: 5, Msg: `请求过快,请稍后重试`}
} }
return nil return nil
@ -69,7 +75,7 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
} }
} }
val := newRateLimit() val := newRateLimit()
if err := env.Cache.Set(rip, val, block_int); err != nil { if err := env.Cache.Set(rip, val, block_mem); err != nil {
loger.Error(`写入内存: %s`, err) loger.Error(`写入内存: %s`, err)
return &resp.Resp{Code: 4, Msg: `速率限制内部异常,请联系网站管理员`} return &resp.Resp{Code: 4, Msg: `速率限制内部异常,请联系网站管理员`}
} }

View File

@ -48,7 +48,7 @@ const httpRequest = (url, options) => new Promise((resolve, reject) => {
const musicUrl = async (source, info, quality) => { const musicUrl = async (source, info, quality) => {
const start = new Date().getTime(); const start = new Date().getTime();
const id = info.hash ?? info.copyrightId ?? info.songmid // 音乐id kg源为hash, mg源为copyrightId const id = info.hash ?? info.copyrightId ?? info.songmid // 音乐id kg源为hash, mg源为copyrightId
const ext = source == 'kg' ? info.albumId : source == 'tx' ? info.strMediaMid : '' const ext = source == 'kg' ? info.albumId : '' //source == 'tx' ? info.strMediaMid
const query = `${source}/${id}${(ext != '' && ext != void 0) ? '-' + ext : ''}/${quality}` const query = `${source}/${id}${(ext != '' && ext != void 0) ? '-' + ext : ''}/${quality}`
console.log('创建任务: %s, 音乐信息: %O', query, info) console.log('创建任务: %s, 音乐信息: %O', query, info)
const body = await httpRequest(`${apiaddr}link/${query}`, { method: 'get' }); const body = await httpRequest(`${apiaddr}link/${query}`, { method: 'get' });

View File

@ -21,22 +21,26 @@ var (
defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`} defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`}
// 试听音质 // 试听音质
tstQuality = []string{`128k`} tstQuality = []string{`128k`}
// 标准音质
stdQuality = []string{`128k`, `320k`, `flac`}
) )
// 自动生成支持的音质表 // 自动生成支持的音质表
func loadQMap() [][]string { func loadQMap() [][]string {
m := make([][]string, 6) m := make([][]string, 6)
// 0.wy // 0.wy
m[0] = defQuality if env.Config.Custom.Wy_Enable {
m[0] = defQuality
}
// 1.mg // 1.mg
m[1] = defQuality m[1] = defQuality
// 2.kw // 2.kw
m[2] = []string{`128k`, `320k`, `flac`} m[2] = stdQuality
// 3.kg // 3.kg
m[3] = tstQuality m[3] = tstQuality
// 4.tx // 4.tx
if env.Config.Custom.Tx_Enable { if env.Config.Custom.Tx_Enable {
m[4] = defQuality m[4] = stdQuality
} else { } else {
m[4] = tstQuality m[4] = tstQuality
} }
@ -64,11 +68,11 @@ func InitRouter() *gin.Engine {
`github`: `https://github.com/ZxwyWebSite/lx-source`, `github`: `https://github.com/ZxwyWebSite/lx-source`,
// 可用平台 // 可用平台
`source`: gin.H{ `source`: gin.H{
`wy`: qmap[0], //true, sources.S_wy: qmap[0], //true,
`mg`: qmap[1], //true, sources.S_mg: qmap[1], //true,
`kw`: qmap[2], //true, sources.S_kw: qmap[2], //true,
`kg`: qmap[3], //[]string{`128k`, `320k`}, // 测试结构2, 启用时返回音质列表, 禁用为false sources.S_kg: qmap[3], //[]string{`128k`, `320k`}, // 测试结构2, 启用时返回音质列表, 禁用为false
`tx`: qmap[4], //gin.H{ // "测试结构 不代表最终方式" sources.S_tx: qmap[4], //gin.H{ // "测试结构 不代表最终方式"
// `enable`: false, // `enable`: false,
// `qualitys`: []string{`128k`, `320k`, `flac`, `flac24bit`}, // `qualitys`: []string{`128k`, `320k`, `flac`, `flac24bit`},
// }, // },
@ -177,8 +181,8 @@ func linkHandler(c *gin.Context) {
env.Cache.Set(cquery.Query(), ``, 600) // 发生错误的10分钟内禁止再次查询 env.Cache.Set(cquery.Query(), ``, 600) // 发生错误的10分钟内禁止再次查询
return &resp.Resp{Code: 2, Msg: emsg} return &resp.Resp{Code: 2, Msg: emsg}
} }
// 缓存并获取直链 // 缓存并获取直链 !(s == `kg` || (s == `tx` && !tx_en)) => (s != `kg` && (s != `tx` || tx_en))
if outlink != `` && cstat && !ztool.Chk_IsMatch(cquery.Source, `kg`, `tx`) { if outlink != `` && cstat && cquery.Source != sources.S_kg && (cquery.Source != sources.S_tx || env.Config.Custom.Tx_Enable) {
sc.Debug(`Method: Set, Link: %v`, outlink) sc.Debug(`Method: Set, Link: %v`, outlink)
if link := caches.UseCache.Set(cquery, outlink); link != `` { if link := caches.UseCache.Set(cquery, outlink); link != `` {
env.Cache.Set(cquery.Query(), link, 3600) env.Cache.Set(cquery.Query(), link, 3600)

View File

@ -25,7 +25,7 @@ func (s *Source) Verify(c *caches.Query) (rquery string, ok bool) {
var ( var (
// 并发对象池 (用户限制在Router处实现) // 并发对象池 (用户限制在Router处实现)
wy_pool = &sync.Pool{New: func() any { return new(FyApi_Song) }} wy_pool = &sync.Pool{New: func() any { return new(WyApi_Song) }}
mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }} mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }}
kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }} kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }}
kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }} kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }}
@ -35,12 +35,13 @@ var (
const ( const (
errHttpReq = `无法连接解析接口` errHttpReq = `无法连接解析接口`
errNoLink = `无法获取试听链接` errNoLink = `无法获取试听链接`
errDisable = `该音乐源已被禁用`
) )
// 查询 // 查询
func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) { func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) {
rquery, ok := s.Verify(c) rquery, ok := s.Verify(c)
if !ok { if !ok /*&& c.Source != `tx`*/ {
msg = sources.Err_Verify //`Verify Failed` msg = sources.Err_Verify //`Verify Failed`
return return
} }
@ -48,14 +49,20 @@ func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) {
jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`) jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`)
switch c.Source { switch c.Source {
case s_wy: case s_wy:
resp := wy_pool.Get().(*FyApi_Song) if !env.Config.Custom.Wy_Enable {
msg = errDisable
return
}
resp := wy_pool.Get().(*WyApi_Song)
defer wy_pool.Put(resp) defer wy_pool.Put(resp)
url := ztool.Str_FastConcat(`http://`, api_wy, `?id=`, c.MusicID, `&level=`, rquery, `&noCookie=true`) // url := ztool.Str_FastConcat(`http://`, api_wy, `?id=`, c.MusicID, `&level=`, rquery, `&noCookie=true`)
url := ztool.Str_FastConcat(`https://`, api_wy, `&id=`, c.MusicID, `&level=`, rquery, `&encodeType=`, c.Extname)
// jx.Debug(`Wy, Url: %v`, url) // jx.Debug(`Wy, Url: %v`, url)
// wy源增加后端重试 默认3次 // wy源增加后端重试 默认3次
for i := 0; true; i++ { for i := 0; true; i++ {
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_wy, &resp) // _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_wy, &resp)
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, nil, &resp)
if err != nil { if err != nil {
jx.Error(`HttpReq, Err: %s, ReTry: %v`, err, i) jx.Error(`HttpReq, Err: %s, ReTry: %v`, err, i)
if i > 3 { if i > 3 {

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,11 @@
package tx package tx
import (
"lx-source/src/env"
"github.com/ZxwyWebSite/ztool/logs"
)
// func Info(songMid string) (info any, msg string) { // func Info(songMid string) (info any, msg string) {
// req, emsg := getMusicInfo(songMid) // req, emsg := getMusicInfo(songMid)
// if emsg != `` { // if emsg != `` {
@ -23,3 +29,12 @@ package tx
// } // }
// return // return
// } // }
func Init() {
if env.Config.Custom.Tx_Refresh_Enable {
env.Tasker.Add(`refresh_login`, func(l *logs.Logger) error {
refresh(l)
return nil
}, 86000, true)
}
}

View File

@ -218,30 +218,3 @@ func getMusicInfo(songMid string) (infoBody musicInfo, emsg string) {
infoBody = infoResp.Req.Data infoBody = infoResp.Req.Data
return //infoBody.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
// }

View File

@ -2,6 +2,7 @@ package tx
import ( import (
"lx-source/src/env" "lx-source/src/env"
"lx-source/src/sources"
"strings" "strings"
"github.com/ZxwyWebSite/ztool" "github.com/ZxwyWebSite/ztool"
@ -85,21 +86,25 @@ type playInfo struct {
if没有链接: if没有链接:
报错 报错
返回结果 返回结果
更新
可通过 goto loop 实现但可能会导致逻辑混乱 (想使用账号获取正常链接却返回试听链接)
*/ */
func Url(songMid, quality string) (ourl, msg string) { func Url(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Tx`)
infoFile, ok := fileInfo[quality] infoFile, ok := fileInfo[quality]
if !ok { if !ok || (!env.Config.Custom.Tx_Enable && quality != sources.Q_128k) {
msg = `不支持的音质` msg = `不支持的音质`
return return
} }
infoBody, emsg := getMusicInfo(songMid) infoBody, emsg := getMusicInfo(songMid)
loger.Debug(`infoBody: %+v`, infoBody)
if emsg != `` { if emsg != `` {
msg = emsg msg = emsg
return return
} }
var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin
if uuin == `` { if uuin == `` || !env.Config.Custom.Tx_Enable {
uuin = `1535153710` uuin = `1535153710`
} }
var strFileName string var strFileName string
@ -122,6 +127,7 @@ func Url(songMid, quality string) (ourl, msg string) {
msg = err.Error() msg = err.Error()
return return
} }
loger.Debug(`infoResp: %+v`, infoResp)
infoData := infoResp.Req0.Data.Midurlinfo[0] infoData := infoResp.Req0.Data.Midurlinfo[0]
if infoData.Purl == `` { if infoData.Purl == `` {
msg = `无法获取音乐链接` msg = `无法获取音乐链接`

View File

@ -0,0 +1,147 @@
package tx
import (
"lx-source/src/env"
"net/http"
"strings"
"time"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/logs"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
)
/*type AutoGenerated struct {
Code int `json:"code"`
Ts int64 `json:"ts"`
StartTs int64 `json:"start_ts"`
Traceid string `json:"traceid"`
Req1 struct {
Code int `json:"code"`
Data struct {
Openid string `json:"openid"`
RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token"`
ExpiredAt int `json:"expired_at"`
Musicid int `json:"musicid"`
MusicKey string `json:"musickey"`
MusickeyCreateTime int `json:"musickeyCreateTime"`
FirstLogin int `json:"first_login"`
ErrMsg string `json:"errMsg"`
SessionKey string `json:"sessionKey"`
Unionid string `json:"unionid"`
StrMusicId string `json:"str_musicid"`
Errtip string `json:"errtip"`
Nick string `json:"nick"`
Logo string `json:"logo"`
FeedbackURL string `json:"feedbackURL"`
EncryptUin string `json:"encryptUin"`
Userip string `json:"userip"`
LastLoginTime int `json:"lastLoginTime"`
KeyExpiresIn int `json:"keyExpiresIn"`
RefreshKey string `json:"refresh_key"`
LoginType int `json:"loginType"`
Prompt2Bind int `json:"prompt2bind"`
LogoffStatus int `json:"logoffStatus"`
OtherAccounts []interface{} `json:"otherAccounts"`
OtherPhoneNo string `json:"otherPhoneNo"`
Token string `json:"token"`
IsPrized int `json:"isPrized"`
IsShowDevManage int `json:"isShowDevManage"`
ErrTip2 string `json:"errTip2"`
Tip3 string `json:"tip3"`
EncryptedPhoneNo string `json:"encryptedPhoneNo"`
PhoneNo string `json:"phoneNo"`
} `json:"data"`
} `json:"req1"`
}*/
type refreshData struct {
Req1 struct {
Code int `json:"code"`
Data struct {
ExpiredAt int64 `json:"expired_at"` // 过期时间 (Unix)
// MusicId int `json:"musicid"` // 数字uid
MusicKey string `json:"musickey"` // 账号Key
StrMusicId string `json:"str_musicid"` // 字符串uid
// KeyExpiresIn int `json:"keyExpiresIn"` // 过期时间 (秒)
} `json:"data"`
} `json:"req1"`
}
/*
刷新登录模块 (移植自Python版)
逻辑
1. 使用内存缓存设置过期时间每次获取链接时取值检查若没有设置或已过期则尝试刷新Key
2. 以计划任务方式运行每隔一段时间自动执行
第一次载入时会刷新一次测试可用性&同步过期时间 (默认7天)
*/
func refresh(loger *logs.Logger) {
// loger := env.Loger.NewGroup(`refresh_login`)
if env.Config.Custom.Tx_Ukey == `` || !env.Config.Custom.Tx_Refresh_Enable {
return
}
if time.Now().Unix() < env.Config.Custom.Tx_Refresh_Interval {
loger.Debug(`Key未过期跳过...`)
return
}
var body, surl string
if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `W_X`) {
body = ztool.Str_FastConcat(
`{"comm":{"authst":"","ct":"11","cv":"12080008","fPersonality":"0","qq":"","tmeAppID":"qqmusic","tmeLoginMethod":"1","tmeLoginType":"1","v":"12080008"},"req1":{"method":"Login","module":"music.login.LoginServer","param":{"code":"","loginMode":2,"musickey":"`,
env.Config.Custom.Tx_Ukey,
`","openid":"","refresh_key":"","refresh_token":"","str_musicid":"`,
env.Config.Custom.Tx_Uuin,
`","unionid":""}}}`,
)
} else if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `Q_H_L`) {
body = ztool.Str_FastConcat(
`{"req1":{"method":"QQLogin","module":"QQConnectLogin.LoginServer","param":{"expired_in":7776000,"musicid":`,
env.Config.Custom.Tx_Uuin,
`,"musickey":"`,
env.Config.Custom.Tx_Ukey,
`"}}}`,
)
surl = `6`
} else {
loger.Error(`未知的qqmusic_key格式`)
env.Config.Custom.Tx_Refresh_Enable = false // 本次启动阻止继续执行
return
}
loger.Debug(`Body: %v`, body)
var resp refreshData
signature := sign(bytesconv.StringToBytes(body))
err := ztool.Net_Request(http.MethodPost,
ztool.Str_FastConcat(`https://u`, surl, `.y.qq.com/cgi-bin/musics.fcg?sign=`, signature),
strings.NewReader(body),
[]ztool.Net_ReqHandlerFunc{
ztool.Net_ReqAddHeaders(header),
},
[]ztool.Net_ResHandlerFunc{
ztool.Net_ResToStruct(&resp),
},
)
if err != nil {
loger.Error(`请求Api失败: %s`, err)
return
}
if resp.Req1.Code != 0 {
loger.Warn("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
return
}
loger.Info(`刷新登录成功`)
env.Config.Custom.Tx_Uuin = resp.Req1.Data.StrMusicId
env.Config.Custom.Tx_Ukey = resp.Req1.Data.MusicKey
env.Config.Custom.Tx_Refresh_Interval = resp.Req1.Data.ExpiredAt - 86000 // 提前一天
loger.Debug(`Resp: %+v`, resp)
loger.Debug(`Uuin: %v, Ukey: %v`, resp.Req1.Data.StrMusicId, resp.Req1.Data.MusicKey)
loger.Debug(`ExpiresAt: %v`, resp.Req1.Data.ExpiredAt)
err = env.Cfg.Save(``)
if err != nil {
loger.Error(`%s`, err)
return
}
loger.Info(`数据更新成功`) // 已通过相应数据更新uin和qqmusic_key
}

View File

@ -46,6 +46,9 @@ var (
`Q000`: `dolby`, `Q000`: `dolby`,
`AI00`: `master`, `AI00`: `master`,
} }
header = map[string]string{
`Referer`: `https://y.qq.com/`,
}
) )
func signRequest(data []byte, out any) error { func signRequest(data []byte, out any) error {
@ -54,9 +57,7 @@ func signRequest(data []byte, out any) error {
ztool.Str_FastConcat(`https://u.y.qq.com/cgi-bin/musics.fcg?format=json&sign=`, s), ztool.Str_FastConcat(`https://u.y.qq.com/cgi-bin/musics.fcg?format=json&sign=`, s),
bytes.NewReader(data), bytes.NewReader(data),
[]ztool.Net_ReqHandlerFunc{ []ztool.Net_ReqHandlerFunc{
ztool.Net_ReqAddHeaders(map[string]string{ ztool.Net_ReqAddHeaders(header),
`Referer`: `https://y.qq.com/`,
}),
}, },
[]ztool.Net_ResHandlerFunc{ []ztool.Net_ResHandlerFunc{
ztool.Net_ResToStruct(out), ztool.Net_ResToStruct(out),

View File

@ -1,5 +1,19 @@
## Lx-Source/更新日志 ## Lx-Source/更新日志
#### \# 2024-01-07 v1.0.2-b0.9 (beta)
<!-- + 不再导出 `public` 目录,源脚本统一到 `/lx-custom-source.js` 获取
+ 为每个源单独设置直链缓存时间 -->
+ 开启tx源账号解析时启用文件缓存
+ 移植tx源刷新登录功能
+ 注之前没有登录过手机qq音乐的账号签到可免费领绿钻会员
+ **已知问题生成直链会暴露uin且无法移除共享时请务必使用缓存**
+ 完善速率限制功能:增加容忍限度、封禁时间,详见配置注释
+ 准备弃用"MusicId-字符串"传附加参数的方式
+ 计划:重构音质对应表部分,每个源使用独立音质表
+ 更换wy内置接口为qz源
+ 默认监听地址改为127.0.0.1
<!-- + *内置接口失效暂时禁用wy源 (如恢复可修改 [Custom].Wy_Enable=true)* -->
#### \# 2024-01-01 v1.0.2-b0.8 (beta) #### \# 2024-01-01 v1.0.2-b0.8 (beta)
+ 注:新年第一次更新,祝大家听歌愉快 + 注:新年第一次更新,祝大家听歌愉快
- 根据源启用状态生成支持音质表 - 根据源启用状态生成支持音质表