update qqmusic wechat login

This commit is contained in:
aynakeya
2025-09-29 23:05:07 +08:00
parent 15cadb4e5f
commit 8f67b50eae
9 changed files with 193 additions and 7 deletions

View File

@@ -10,12 +10,13 @@ import (
_ "github.com/AynaLivePlayer/miaosic/providers/kuwo"
_ "github.com/AynaLivePlayer/miaosic/providers/local"
_ "github.com/AynaLivePlayer/miaosic/providers/netease"
_ "github.com/AynaLivePlayer/miaosic/providers/qq"
"github.com/AynaLivePlayer/miaosic/providers/qq"
"github.com/spf13/cobra"
)
func init() {
kugou.UseInstrumental()
qq.UseQQLogin()
}
var rootCmd = &cobra.Command{

View File

@@ -8,5 +8,14 @@ import (
func init() {
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
miaosic.RegisterProvider(NewQQMusicProvider())
// user should manually register provider since there are two channel
//miaosic.RegisterProvider(NewQQMusicProvider("qq"))
}
func UseQQLogin() {
miaosic.RegisterProvider(NewQQMusicProvider("qq"))
}
func UseWechatLogin() {
miaosic.RegisterProvider(NewQQMusicProvider("wechat"))
}

View File

@@ -62,12 +62,16 @@ func (p *QQMusicProvider) refreshToken() error {
}
func (p *QQMusicProvider) QrLogin() (*miaosic.QrLoginSession, error) {
// todo finish wechat qrlogin channel
if p.channel == "wechat" {
return p.getWxQR()
}
return p.getQQQR()
}
func (p *QQMusicProvider) QrLoginVerify(qrlogin *miaosic.QrLoginSession) (*miaosic.QrLoginResult, error) {
// todo finish wechat qrlogin channel
if p.channel == "wechat" {
return p.checkWxQR(qrlogin)
}
return p.checkQQQR(qrlogin)
}

View File

@@ -250,7 +250,20 @@ func (p *QQMusicProvider) getCredentialWithCode(code string, loginType int) (*mi
"code": code,
}
data, err := p.makeApiRequest("QQConnectLogin.LoginServer", "QQLogin", params)
var module, method string
// 微信登录
if loginType == 1 {
params["strAppid"] = "wx48db31d50e334801"
module = "music.login.LoginServer"
method = "Login"
} else if loginType == 2 {
// QQ登录
module = "QQConnectLogin.LoginServer"
method = "QQLogin"
}
data, err := p.makeApiRequest(module, method, params)
if err != nil {
return nil, err
}

View File

@@ -1 +1,124 @@
package qq
import (
"errors"
"fmt"
"github.com/AynaLivePlayer/miaosic"
"github.com/go-resty/resty/v2"
"github.com/spf13/cast"
_ "image/jpeg" // wechat qrcode is jpg
"net/http"
"regexp"
"time"
)
func (p *QQMusicProvider) getWxQR() (*miaosic.QrLoginSession, error) {
resp, err := miaosic.Requester.GetQuery(
"https://open.weixin.qq.com/connect/qrconnect",
map[string]string{
"appid": "wx48db31d50e334801",
"redirect_uri": "https://y.qq.com/portal/wx_redirect.html?login_type=2&surl=https://y.qq.com/",
"response_type": "code",
"scope": "snsapi_login",
"state": "STATE",
"href": "https://y.qq.com/mediastyle/music_v17/src/css/popup_wechat.css#wechat_redirect",
},
map[string]string{
"Referer": "https://open.weixin.qq.com/connect/qrconnect",
},
)
if err != nil {
return nil, err
}
loginUuid := regexp.MustCompile("uuid=(.+?)\"").FindStringSubmatch(resp.String())
if len(loginUuid) < 2 {
return nil, errors.New("miaosic (qq): failed to get qrcode")
}
resp, err = miaosic.Requester.GetQuery(
"https://open.weixin.qq.com/connect/qrcode/"+loginUuid[1],
nil,
map[string]string{
"Referer": "https://open.weixin.qq.com/connect/qrconnect",
},
)
var qrUrl string
// !!! dont remove, might use in future as a fallback option.
//{
// img, _, err := image.Decode(bytes.NewBuffer(resp.Body()))
// if err != nil {
// return nil, errors.New("miaosic (qq): failed to read qrcode")
// }
//
// bmp, err := gozxing.NewBinaryBitmapFromImage(img)
// if err != nil {
// return nil, errors.New("miaosic (qq): failed to read qrcode to bmp")
// }
//
// qrReader := qrcode.NewQRCodeReader()
//
// result, err := qrReader.Decode(bmp, nil)
// if err != nil {
// return nil, errors.New("miaosic (qq): failed to decode qrcode")
// }
// qrUrl = result.GetText()
//}
{
qrUrl = "https://open.weixin.qq.com/connect/confirm?uuid=" + loginUuid[1]
}
return &miaosic.QrLoginSession{
Url: qrUrl,
Key: loginUuid[1],
}, nil
}
func (p *QQMusicProvider) checkWxQR(qrlogin *miaosic.QrLoginSession) (*miaosic.QrLoginResult, error) {
resp, err := resty.New().SetTimeout(time.Second * 2).
R().
SetQueryParams(map[string]string{
"uuid": qrlogin.Key,
"_": fmt.Sprintf("%d", time.Now().UnixMilli()),
}).
SetHeaders(map[string]string{
"Referer": "https://open.weixin.qq.com/",
}).Get("https://lp.open.weixin.qq.com/connect/l/qrconnect")
if err != nil {
if errors.Is(err, http.ErrHandlerTimeout) {
return &miaosic.QrLoginResult{Success: false, Message: "timeout, might be waiting for scan"}, nil
}
return &miaosic.QrLoginResult{Success: false, Message: "unknown error"}, err
}
//pp.Println(resp.String())
result := regexp.MustCompile(`window\.wx_errcode=(\d+);window\.wx_code=\'([^\']*)\'`).FindStringSubmatch(resp.String())
if len(result) < 3 {
return &miaosic.QrLoginResult{
Success: false,
Message: "fail to check qr status",
}, errors.New("miaosic (qq): fail to check qr status")
}
//pp.Println(result)
statusCode, err := cast.ToIntE(result[1])
if err != nil {
return &miaosic.QrLoginResult{
Success: false,
Message: "invalid status code",
}, fmt.Errorf("miaosic (qq): invalid qr status code: %s", result[1])
}
switch statusCode {
case 405:
return p.getCredentialWithCode(result[2], 1)
case 408:
return &miaosic.QrLoginResult{Success: false, Message: "等待扫描二维码"}, nil
case 404:
return &miaosic.QrLoginResult{Success: false, Message: "扫描未确认登陆"}, nil
//case 65:
// return &miaosic.QrLoginResult{Success: false, Message: "二维码已过期"}, nil
case 403:
return &miaosic.QrLoginResult{Success: false, Message: "! 拒绝登陆 !"}, nil
default:
return &miaosic.QrLoginResult{Success: false, Message: fmt.Sprintf("未知错误 %d", statusCode)}, nil
}
}

View File

@@ -0,0 +1,24 @@
package qq
import (
"github.com/AynaLivePlayer/miaosic"
"github.com/k0kubun/pp/v3"
"github.com/stretchr/testify/require"
"testing"
)
func TestQQ_getQrcodeWx(t *testing.T) {
result, err := testApi.getWxQR()
require.NoError(t, err)
require.NotEmpty(t, result)
pp.Println(result)
}
func TestQQ_checkWxQR(t *testing.T) {
_, err := testApi.checkWxQR(&miaosic.QrLoginSession{
Url: "https://open.weixin.qq.com/connect/confirm?uuid=071Yrpg10iD00w3H",
Key: "071Yrpg10iD00w3H",
})
require.NoError(t, err)
//pp.Println(result)
}

View File

@@ -25,6 +25,7 @@ type QQMusicProvider struct {
header map[string]string
qimeiUpdated bool //i don't care concurrence
tokenRefreshed bool
channel string // "qq" or "wechat"
}
func (p *QQMusicProvider) GetName() string {
@@ -41,7 +42,10 @@ func (p *QQMusicProvider) Qualities() []miaosic.Quality {
}
}
func NewQQMusicProvider() *QQMusicProvider {
func NewQQMusicProvider(channel string) *QQMusicProvider {
if channel != "qq" && channel != "wechat" {
channel = "qq"
}
val := &QQMusicProvider{
cfg: ApiConfig{
Version: "13.2.5.8",
@@ -58,6 +62,7 @@ func NewQQMusicProvider() *QQMusicProvider {
},
qimeiUpdated: false,
tokenRefreshed: false,
channel: channel,
}
return val
}

View File

@@ -4,3 +4,4 @@ this part of code is inspired by
- https://github.com/fred913/goqrcdec under Apache License
qr login code is piece of shit. trying to find better way to do this

View File

@@ -46,6 +46,12 @@ func (p *QQMusicProvider) makeApiRequest(module, method string, params map[strin
cookie := map[string]interface{}{}
if p.cred.LoginType != 0 {
common["tmeLoginType"] = strconv.Itoa(p.cred.GetFormatedLoginType())
}
//pp.Println(common)
if p.cred.HasMusicKey() && p.cred.HasMusicID() {
common["authst"] = p.cred.MusicKey
common["qq"] = p.cred.MusicID
@@ -98,7 +104,7 @@ func (p *QQMusicProvider) makeApiRequest(module, method string, params map[strin
return gjson.Result{}, err
}
jsonResp := gjson.ParseBytes(response.Body())
//fmt.Println(response.String())
//pp.Println(response.String())
moduleKeyEscaped := strings.ReplaceAll(moduleKey, ".", "\\.")
if !jsonResp.Get(moduleKeyEscaped).Exists() {
return gjson.Result{}, fmt.Errorf("miaosic (qq): api request fail")