diff --git a/go.mod b/go.mod index e63e679..06aa77b 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module lx-source go 1.21 require ( + github.com/ZxwyWebSite/cr-go-sdk v0.0.2 github.com/ZxwyWebSite/ztool v0.0.1 github.com/gin-contrib/gzip v1.0.0 github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.6.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e ) @@ -43,4 +45,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/ZxwyWebSite/ztool v0.0.1 => ./pkg/ztool // ../ztool +replace ( + github.com/ZxwyWebSite/cr-go-sdk v0.0.2 => ../cr-go-sdk + github.com/ZxwyWebSite/ztool v0.0.1 => ./pkg/ztool // ../ztool +) diff --git a/go.sum b/go.sum index 0c50787..a40af1b 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= diff --git a/init.go b/init.go index d7e2429..21ed784 100644 --- a/init.go +++ b/init.go @@ -3,6 +3,7 @@ package main import ( "encoding/base64" "lx-source/src/caches" + "lx-source/src/caches/cloudcache" "lx-source/src/caches/localcache" "lx-source/src/env" @@ -12,6 +13,7 @@ import ( stdurl "net/url" "path/filepath" + "github.com/ZxwyWebSite/cr-go-sdk" "github.com/ZxwyWebSite/ztool" "github.com/ZxwyWebSite/ztool/logs" "github.com/ZxwyWebSite/ztool/zcypt" @@ -169,22 +171,31 @@ func initMain() { // } // 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, - // } + icl.Warn(`欢迎使用新版 Cloudreve 驱动, 由 cr-go-sdk 提供强力支持`) + site := &cr.SiteObj{ + Addr: env.Config.Cache.Cloud_Site, + ApiVer: cr.ApiV383, + Users: &cr.UserObj{ + Mail: env.Config.Cache.Cloud_User, + Pass: env.Config.Cache.Cloud_Pass, + Cookie: cr.ParseCookie(env.Config.Cache.Cloud_Sess), + }, + } + cache, err := caches.New(&cloudcache.Cache{ + Site: site, + Path: env.Config.Cache.Cloud_Path, + }) + if err != nil { + icl.Error(`驱动["cloudreve"]初始化失败: %v, 将禁用缓存功能`, err) + } else { + env.Tasker.Add(`cloud_sess`, func(l *logs.Logger, i int64) error { + if sess := site.Users.Cookie.String(); sess != env.Config.Cache.Cloud_Sess { + env.Config.Cache.Cloud_Sess = sess + } + return env.Cfg.Save(``) + }, 3600, true) + } + caches.UseCache = cache default: icl.Error(`未定义的缓存模式,请检查配置 [Cache].Mode,本次启动禁用缓存`) } diff --git a/menu.go b/menu.go index 181d40f..1c462aa 100644 --- a/menu.go +++ b/menu.go @@ -1,8 +1,11 @@ package main import ( + "fmt" "lx-source/src/env" + "lx-source/src/sources/custom/tx" wm "lx-source/src/sources/custom/wy/modules" + "runtime" "strings" "time" @@ -22,6 +25,8 @@ func parseEtag(etag *string) { // menuMian() case `wyqr`: wyQrLogin() + case `txqq`: + txQqLogin() default: loger.Fatal(`未知参数:%q`, *etag) } @@ -33,6 +38,17 @@ func wyQrLogin() { loger := env.Loger.NewGroup(`WyQrLogin`) defer loger.Free() loger.Info(`执行模块: 网易云扫码登录`) + + if env.Config.Custom.Wy_Api_Cookie != `` { + loger.Warn("已存在账号数据, 继续操作可能导致数据覆盖丢失!") + fmt.Print(`输入'y'继续: `) + var input string + fmt.Scanln(&input) + if input != `y` { + loger.Fatal(`用户取消操作`) + } + } + res, err := wm.LoginQrKey() if err != nil { loger.Fatal(`无法创建请求: %s`, err) @@ -80,6 +96,32 @@ func wyQrLogin() { } } +// QQ快速登录 +func txQqLogin() { + loger := env.Loger.NewGroup(`TxQqLogin`) + defer loger.Free() + loger.Info(`执行模块: QQ快速登录`) + + if runtime.GOOS != `windows` { + loger.Fatal(`该模块仅支持在windows环境下使用`) + return + } + + if env.Config.Custom.Tx_Ukey != `` { + loger.Warn("已存在账号数据, 继续操作可能导致数据覆盖丢失!") + fmt.Print(`输入'y'继续: `) + var input string + fmt.Scanln(&input) + if input != `y` { + loger.Fatal(`用户取消操作`) + } + } + + if err := tx.Qlogin_graph(loger); err != nil { + loger.Fatal(err.Error()) + } +} + // func menuMian() { // app := menu.NewApp(`Lx-Source`) // app.Data = menu.Data{ diff --git a/src/caches/cloudcache/cloud.go b/src/caches/cloudcache/cloud.go index c547d28..98f9dc7 100644 --- a/src/caches/cloudcache/cloud.go +++ b/src/caches/cloudcache/cloud.go @@ -1 +1,121 @@ package cloudcache + +import ( + "lx-source/src/caches" + "lx-source/src/env" + "net/http" + "strings" + + cr "github.com/ZxwyWebSite/cr-go-sdk" + "github.com/ZxwyWebSite/cr-go-sdk/service/explorer" + "github.com/ZxwyWebSite/ztool" +) + +type Cache struct { + Site *cr.SiteObj + Path string + state bool +} + +func (c *Cache) Get(q *caches.Query) string { + var b strings.Builder + b.WriteString(c.Path) + b.WriteByte('/') + b.WriteString(q.Source) + b.WriteByte('/') + b.WriteString(q.MusicID) + list, err := c.Site.Directory(b.String()) + if err != nil { + caches.Loger.Debug(`列出目录: %v`, err) + return `` + } + name := q.Quality + `.` + q.Extname + var id string + for _, v := range list.Objects { + if v.Name == name && v.Type == `file` { + id = v.ID + break + } + } + if id == `` { + caches.Loger.Debug(`文件不存在`) + return `` + } + srcs, err := c.Site.FileSource(cr.GenerateSrc(false, id)) + if err != nil { + caches.Loger.Debug(`生成外链: %v`, err) + return `` + } + return (*srcs)[0].URL + /*link, err := c.Site.FileDownload(id) + if err != nil { + caches.Loger.Debug(`下载文件: %v`, err) + return `` + } + if (*link)[0] == '/' { + return c.Site.Addr + (*link)[1:] + } + return *link*/ +} + +func (c *Cache) Set(q *caches.Query, l string) string { + var b strings.Builder + b.WriteString(c.Path) + b.WriteByte('/') + b.WriteString(q.Source) + b.WriteByte('/') + b.WriteString(q.MusicID) + dir := b.String() + err := c.Site.DirectoryNew(&explorer.DirectoryService{ + Path: dir, + }) + if err != nil { + caches.Loger.Debug(`创建目录: %v`, err) + return `` + } + /*var buf bytes.Buffer + err = ztool.Net_Download(l, &buf, nil) + if err != nil { + caches.Loger.Debug(`下载文件: %v`, err) + return `` + }*/ + name := q.Quality + `.` + q.Extname + err = ztool.Net_Request( + http.MethodGet, l, nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + return (&cr.UploadTask{ + Site: c.Site, + File: res.Body, + Size: uint64(res.ContentLength), + Name: name, + Mime: `audio/mpeg`, + }).Do(dir) + }}, + ) + if err != nil { + caches.Loger.Debug(`上传文件: %v`, err) + return `` + } + return c.Get(q) +} + +func (c *Cache) Stat() bool { + return c.state +} + +func (c *Cache) Init() error { + cr.Cr_Debug = env.Config.Main.Debug + err := c.Site.SdkInit() + if err != nil { + return err + } + if c.Site.Users.Cookie == nil || c.Site.Config.User.Anonymous { + err = c.Site.SdkLogin() + if err != nil { + return err + } + } + c.state = true + return nil +} diff --git a/src/env/env.go b/src/env/env.go index 73bfc00..53a307f 100644 --- a/src/env/env.go +++ b/src/env/env.go @@ -12,7 +12,7 @@ import ( ) const ( - Version = `1.0.3.0430` + Version = `1.0.3.0518` ) var ( @@ -36,10 +36,10 @@ type ( Print bool `comment:"控制台输出 (影响io性能,后台使用建议关闭)"` SysLev bool `comment:"(实验性) 设置进程高优先级"` // FFConv bool `comment:"(实验性) 使用FFMpeg修复音频(本地缓存)"` - NgProxy bool `comment:"兼容反向代理(beta)"` - Timeout int64 `comment:"网络请求超时(单位:秒,海外服务器可适当调大)"` - - Store string `comment:"内存缓存持久化文件地址"` + NgProxy bool `comment:"兼容反向代理(beta)"` + Timeout int64 `comment:"网络请求超时(单位:秒,海外服务器可适当调大)"` + Store string `comment:"内存缓存持久化文件地址"` + ErrMp3 string `comment:"获取失败默认音频"` } // 接口 Conf_Apis struct { @@ -174,11 +174,11 @@ type ( 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 { @@ -204,6 +204,7 @@ var ( SysLev: false, Timeout: 30, Store: `/data/memo.bin`, + ErrMp3: `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3`, }, Apis: Conf_Apis{ // BindAddr: `http://192.168.10.22:1011/`, @@ -276,11 +277,11 @@ var ( 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/resp/resp.go b/src/middleware/resp/resp.go index c64e223..3ffd860 100644 --- a/src/middleware/resp/resp.go +++ b/src/middleware/resp/resp.go @@ -2,6 +2,7 @@ package resp import ( + "lx-source/src/env" "net/http" "github.com/gin-gonic/gin" @@ -26,7 +27,7 @@ type Resp struct { } // 获取失败默认音频 -var ErrMp3 = `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3` +// var ErrMp3 = `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3` // 返回请求 /* @@ -43,7 +44,7 @@ func (o *Resp) Execute(c *gin.Context) { case 2: status = http.StatusServiceUnavailable if o.Data == nil || o.Data == `` { - o.Data = ErrMp3 + o.Data = env.Config.Main.ErrMp3 //ErrMp3 } case 3: status = http.StatusUnauthorized diff --git a/src/server/api_music.go b/src/server/api_music.go index 3166b00..ce53b1f 100644 --- a/src/server/api_music.go +++ b/src/server/api_music.go @@ -103,6 +103,7 @@ func musicHandler(c *gin.Context) { cstat = caches.UseCache.Stat() } uquery := caches.NewQuery(ps, pid, pq) + uquery.Request = c.Request defer uquery.Free() if cstat { loger.Debug(`FileGet: %v`, uquery.Query()) diff --git a/src/server/public/status.html b/src/server/public/status.html index f0bb9d8..d133bde 100644 --- a/src/server/public/status.html +++ b/src/server/public/status.html @@ -16,7 +16,7 @@

示例:

const api = './'; function l(s, id, q) { let url = `${api}link/${s}/${id[Math.floor(Math.random() * id.length)]}/${q}`; - const key = localStorage.getItem('apipass'); if (key) url += `?key=${key}`; + const key = localStorage.getItem('apipass'); if (key) url += `?key=${encodeURIComponent(key)}`; fetch(url) .then((response) => response.json()) .then((res) => { diff --git a/src/sources/custom/kw/player.go b/src/sources/custom/kw/player.go index 93a176e..c4185ce 100644 --- a/src/sources/custom/kw/player.go +++ b/src/sources/custom/kw/player.go @@ -110,12 +110,12 @@ func kwdes(songMid, quality string) (ourl, msg string) { target_url := ztool.Str_FastConcat( `https://mobi.kuwo.cn/mobi.s?f=kuwo&q=`, base64_encrypt(ztool.Str_FastConcat( - `user=0&android_id=0&prod=kwplayer_ar_8.5.5.0&corp=kuwo&newver=3&vipver=8.5.5.0&source=kwplayer_ar_8.5.5.0_apk_keluze.apk&p2p=1¬race=0`, + `corp=kuwo&source=kwplayer_ar_1.1.9_oppo_118980_320.apk&p2p=1&sig=0`, `&type=`, convtype, `&br=`, infoFile.H, infoFile.E, `&format=`, infoFile.E, `&rid=`, songMid, - `&priority=bitrate&loginUid=0&network=WIFI&loginSid=0&mode=down`, + `¬race=0&priority=bitrate&network=WIFI&mode=down`, )), ) if parsemod { @@ -138,7 +138,7 @@ func kwdes(songMid, quality string) (ourl, msg string) { return } realQuality := strconv.Itoa(resp.Data.Bitrate) - if realQuality != infoFile.H[:len(infoFile.H)-1] { + if realQuality != infoFile.H[:len(infoFile.H)-1] /*&& resp.Data.Bitrate != 1*/ { msg = sources.E_QNotMatch if !env.Config.Source.ForceFallback { return diff --git a/src/sources/custom/tx/login.go b/src/sources/custom/tx/login.go new file mode 100644 index 0000000..5b0acdb --- /dev/null +++ b/src/sources/custom/tx/login.go @@ -0,0 +1,355 @@ +package tx + +import ( + "bytes" + "errors" + "fmt" + "io" + "lx-source/src/env" + "math/rand" + "net/http" + "net/http/cookiejar" + "net/url" + "strconv" + "strings" + "time" + _ "unsafe" + + "github.com/ZxwyWebSite/ztool" + "github.com/ZxwyWebSite/ztool/logs" + "github.com/ZxwyWebSite/ztool/x/json" + "github.com/google/uuid" +) + +//go:linkname request github.com/ZxwyWebSite/ztool.request +func request(client *http.Client, method, url string, body io.Reader, reqh []ztool.Net_ReqHandlerFunc, resh []ztool.Net_ResHandlerFunc) error + +// QQ快速登录 - 直接使用本机已登录账号 +func Qlogin_graph(l *logs.Logger) error { + // 参考文章: https://learnku.com/articles/33970 + + jar, _ := cookiejar.New(nil) + client := &http.Client{ + Timeout: time.Second * 10, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Jar: jar, + } + + // Step0 - 获取本机QQ服务地址 + // (应该没有人会占用4301端口导致qq切换备用端口吧,先不写了...懒) + err := request( + client, http.MethodGet, `https://localhost.ptlogin2.qq.com:4301/pc_querystatus`, nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + _, err := io.Copy(io.Discard, res.Body) + return err + }}, + ) + if err != nil { + return errors.New(`step0: 无法连接本机QQ服务`) + } + + // Step1 - 获取 pt_local_token + var pt_local_token string + err = request( + client, http.MethodGet, + `https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&daid=383&style=33&login_text=%E7%99%BB%E5%BD%95&hide_title_bar=1&hide_border=1&target=self&s_url=https://graph.qq.com/oauth2.0/login_jump&pt_3rd_aid=100497308&pt_feedback_link=https://support.qq.com/products/77942?customInfo=.appid100497308&theme=2&verify_theme=`, + nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{ + `Referer`: `https://graph.qq.com/`, + })}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + for _, v := range res.Cookies() { + if v.Name == `pt_local_token` { + pt_local_token = v.Value + break + } + } + _, err := io.Copy(io.Discard, res.Body) + return err + }}, + ) + if err != nil { + return err + } + l.Info(`pt_local_token: %v`, pt_local_token) + + // Step2 - 获取本机登录的 QQ 号 + var url2 strings.Builder + url2.WriteString(`https://localhost.ptlogin2.qq.com:4301/pt_get_uins?callback=ptui_getuins_CB&r=`) + url2.WriteString(strconv.FormatFloat(rand.Float64(), 'f', -1, 64)) + url2.WriteString(`&pt_local_tk=`) + url2.WriteString(pt_local_token) + var out2 []struct { + Uin int `json:"uin"` + // FaceIndex int `json:"face_index"` + // Gender int `json:"gender"` + Nickname string `json:"nickname"` + // ClientType int `json:"client_type"` + // UinFlag int `json:"uin_flag"` + // Account int `json:"account"` + } + var header2 = map[string]string{ + `Referer`: `https://xui.ptlogin2.qq.com/`, + // `Cookie`: `pt_local_token=` + pt_local_token, + } + err = request( + client, http.MethodGet, url2.String(), nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + data, err := io.ReadAll(res.Body) + if err != nil { + return err + } + sep_b := bytes.IndexByte(data, '[') + sep_e := bytes.LastIndexByte(data, ']') + if sep_b == -1 || sep_e == -1 { + return errors.New(`step2: 无法解析返回数据`) + } + sep := data[sep_b : sep_e+1] + return json.Unmarshal(sep, &out2) + }}, + ) + if err != nil { + return err + } + var uin string + length := len(out2) + switch length { + case 0: + return errors.New(`step2: 无可用账号`) + case 1: + uin = strconv.Itoa(out2[0].Uin) + default: + fmt.Println(`请选择要登录的账号:`) + for i, v := range out2 { + fmt.Println(i, v.Nickname, v.Uin) + } + for { + fmt.Print(`输入序号: `) + var input string + fmt.Scanln(&input) + i, err := strconv.Atoi(input) + if err != nil { + l.Error(`err: %v`, err) + continue + } + if i >= length { + l.Error(`err: 下标越界`) + continue + } + uin = strconv.Itoa(out2[i].Uin) + break + } + } + l.Info(`uin: %v`, uin) + + // Step3 - 获取 clientkey + var url3 strings.Builder + url3.WriteString(`https://localhost.ptlogin2.qq.com:4301/pt_get_st?clientuin=`) + url3.WriteString(uin) + url3.WriteString(`&r=`) + url3.WriteString(strconv.FormatFloat(rand.Float64(), 'f', -1, 64)) + url3.WriteString(`&pt_local_tk=`) + url3.WriteString(pt_local_token) + url3.WriteString(`&callback=__jp0`) + // var clientkey string + err = request( + client, http.MethodGet, url3.String(), nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + /*for _, v := range res.Cookies() { + if v.Name == `clientkey` { + clientkey = v.Value + break + } + }*/ + _, err := io.Copy(io.Discard, res.Body) + return err + }}, + ) + if err != nil { + return err + } + + // Step4 - 获取 skey + var url4 strings.Builder + url4.WriteString(`https://ssl.ptlogin2.qq.com/jump?clientuin=`) + url4.WriteString(uin) + url4.WriteString(`&keyindex=9&pt_aid=716027609&daid=383&u1=https://graph.qq.com/oauth2.0/login_jump&pt_local_tk=`) + url4.WriteString(pt_local_token) + url4.WriteString(`&pt_3rd_aid=100497308&ptopt=1&style=40`) + /*var cookie4 strings.Builder + cookie4.WriteString(`pt_local_token=`) + cookie4.WriteString(pt_local_token) + cookie4.WriteByte(';') + cookie4.WriteString(`clientuin=`) + cookie4.WriteString(uin) + cookie4.WriteByte(';') + cookie4.WriteString(`clientkey=`) + cookie4.WriteString(clientkey)*/ + var jurl string + err = request( + client, http.MethodGet, url4.String(), nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders( /*map[string]string{ + `Referer`: `https://xui.ptlogin2.qq.com/`, + `Cookie`: cookie4.String(), + }*/header2)}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + data, err := io.ReadAll(res.Body) + if err != nil { + return err + } + sep_b := bytes.IndexByte(data, ',') + sep_e := bytes.LastIndexByte(data, ' ') + if sep_b == -1 || sep_e == -1 { + return errors.New(`step4: 无法解析返回数据`) + } + jurl = string(data[sep_b+3 : sep_e-2]) + return nil + }}, + ) + if err != nil { + return err + } + + // Step5 - 获取 p_skey + var p_skey string + err = request( + client, http.MethodGet, jurl, nil, + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + for _, v := range res.Cookies() { + if v.Name == `p_skey` && v.Value != `` { + p_skey = v.Value + break + } + } + _, err := io.Copy(io.Discard, res.Body) + return err + }}, + ) + if err != nil { + return err + } + + // Step6 - 登录账号 + getGtk := func(skey string) string { + var hash = 5381 + for _, v := range skey { + hash += (hash << 5) + int(v) + } + return strconv.Itoa(hash & 0x7fffffff) + } + now := time.Now() + var authcode string + err = request( + client, http.MethodPost, + `https://graph.qq.com/oauth2.0/authorize`, + strings.NewReader(ztool.Str_FastConcat( + `response_type=code&client_id=100497308&redirect_uri=https%3A%2F%2Fy.qq.com%2Fportal%2Fwx_redirect.html%3Flogin_type%3D1%26surl%3Dhttps%3A%2F%2Fy.qq.com%2F&scope=get_user_info%2Cget_app_friends&state=state&switch=&from_ptlogin=1&src=1&update_auth=1&openapi=1010_1030`, + `&g_tk=`, getGtk(p_skey), + `&auth_time=`, strconv.FormatInt(now.UnixMilli(), 10), + `&ui=`, strings.ToUpper(uuid.NewString()), + )), + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{ + `Referer`: `https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100497308&redirect_uri=https%3A%2F%2Fy.qq.com%2Fportal%2Fwx_redirect.html%3Flogin_type%3D1%26surl%3Dhttps%3A%2F%2Fy.qq.com%2F&state=state&display=pc&scope=get_user_info%2Cget_app_friends`, + `Content-Type`: `application/x-www-form-urlencoded`, + })}, + []ztool.Net_ResHandlerFunc{func(res *http.Response) error { + /*if res.StatusCode != 302 { + return errors.New(`step6: not redirect`) + }*/ + location := res.Header[`Location`][0] + l.Info(`loc: %v`, location) + loc, err := url.Parse(location) + if err != nil { + return err + } + authcode = loc.Query()[`code`][0] + return nil + }}, + ) + if err != nil { + return err + } + l.Info(`authcode: %v`, authcode) + var out6 struct { + Code int `json:"code"` + // Ts int64 `json:"ts"` + // StartTs int64 `json:"start_ts"` + // Traceid string `json:"traceid"` + Req struct { + Code int `json:"code"` + Data struct { + // 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"` + // BindAccountType int `json:"bindAccountType"` + // NeedRefreshKeyIn int `json:"needRefreshKeyIn"` + } `json:"data"` + } `json:"req"` + } + err = request( + client, http.MethodPost, + `https://u.y.qq.com/cgi-bin/musicu.fcg`, + strings.NewReader(ztool.Str_FastConcat( + `{"comm":{"g_tk":5381,"platform":"yqq","ct":24,"cv":0},"req":{"module":"QQConnectLogin.LoginServer","method":"QQLogin","param":{"code":"`, authcode, `"}}}`, + )), + []ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{ + `Referer`: `https://y.qq.com/`, + `Content-Type`: `application/x-www-form-urlencoded`, + })}, + []ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out6)}, + ) + if err != nil { + return err + } + l.Info(`res: %+v`, out6) + l.Info(`登录成功`) + + env.Config.Custom.Tx_Enable = true + env.Config.Custom.Tx_Uuin = out6.Req.Data.StrMusicid + env.Config.Custom.Tx_Ukey = out6.Req.Data.Musickey + env.Config.Custom.Tx_Refresh_Enable = true + env.Config.Custom.Tx_Refresh_Interval = time.Date(now.Year(), now.Month(), now.Day()+5, 0, 0, 0, 0, now.Location()).Unix() + // env.Config.Custom.Tx_RefreshToken = out6.Req.Data.RefreshToken + // env.Config.Custom.Tx_AccessToken = out6.Req.Data.AccessToken + + return env.Cfg.Save(``) +} + +// QQ扫码登录(todo) +// func qlogin_qr_() diff --git a/src/sources/custom/tx/player.go b/src/sources/custom/tx/player.go index 8a7a929..de368f4 100644 --- a/src/sources/custom/tx/player.go +++ b/src/sources/custom/tx/player.go @@ -103,6 +103,7 @@ Loop: if quality != sources.Q_128k && infoBody.TrackInfo.Pay.PayPlay == 0 { msg = `Fallback to 128k` infoFile = fileInfo[sources.Q_128k] + quality = sources.Q_128k } else { msg = `Fallbacked` tryLink = true diff --git a/update.md b/update.md index 89c089a..9d89f29 100644 --- a/update.md +++ b/update.md @@ -3,6 +3,21 @@ +#### \# 2024-05-18 v1.0.3.0518 (dev) ++ (据用户反馈,当前账号添加方式过于复杂,很多参数不知道怎么填,故增加部分平台简化登录方式) ++ Tx源支持QQ快速登录(beta), 启动参数 `-e txqq` ++ 支持自定义错误音频地址(不填禁用), 位置 [Main].ErrMp3 ++ ~~修复若干已知Bug~~ + + + +#### \# 2024-05-03 v1.0.3.0503 (dev) ++ 基于 cr-go-sdk 重新支持 Cloudreve 缓存 ++ 解决Tx源一处Fallback死循环问题 + #### \# 2024-04-30 v1.0.3.0430 (beta) + Tx源支持自定义CDN链接地址 + Wy源支持扫码登录(beta), 启动参数 `-e wyqr`