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`