mirror of
https://github.com/AynaLivePlayer/miaosic.git
synced 2025-12-06 13:02:48 +08:00
update qqmusic wechat login
This commit is contained in:
@@ -10,12 +10,13 @@ import (
|
|||||||
_ "github.com/AynaLivePlayer/miaosic/providers/kuwo"
|
_ "github.com/AynaLivePlayer/miaosic/providers/kuwo"
|
||||||
_ "github.com/AynaLivePlayer/miaosic/providers/local"
|
_ "github.com/AynaLivePlayer/miaosic/providers/local"
|
||||||
_ "github.com/AynaLivePlayer/miaosic/providers/netease"
|
_ "github.com/AynaLivePlayer/miaosic/providers/netease"
|
||||||
_ "github.com/AynaLivePlayer/miaosic/providers/qq"
|
"github.com/AynaLivePlayer/miaosic/providers/qq"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
kugou.UseInstrumental()
|
kugou.UseInstrumental()
|
||||||
|
qq.UseQQLogin()
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -8,5 +8,14 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
|
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"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,12 +62,16 @@ func (p *QQMusicProvider) refreshToken() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *QQMusicProvider) QrLogin() (*miaosic.QrLoginSession, error) {
|
func (p *QQMusicProvider) QrLogin() (*miaosic.QrLoginSession, error) {
|
||||||
// todo finish wechat qrlogin channel
|
if p.channel == "wechat" {
|
||||||
|
return p.getWxQR()
|
||||||
|
}
|
||||||
return p.getQQQR()
|
return p.getQQQR()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *QQMusicProvider) QrLoginVerify(qrlogin *miaosic.QrLoginSession) (*miaosic.QrLoginResult, error) {
|
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)
|
return p.checkQQQR(qrlogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -250,7 +250,20 @@ func (p *QQMusicProvider) getCredentialWithCode(code string, loginType int) (*mi
|
|||||||
"code": code,
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,124 @@
|
|||||||
package qq
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
24
providers/qq/login_wechat_test.go
Normal file
24
providers/qq/login_wechat_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ type QQMusicProvider struct {
|
|||||||
header map[string]string
|
header map[string]string
|
||||||
qimeiUpdated bool //i don't care concurrence
|
qimeiUpdated bool //i don't care concurrence
|
||||||
tokenRefreshed bool
|
tokenRefreshed bool
|
||||||
|
channel string // "qq" or "wechat"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *QQMusicProvider) GetName() string {
|
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{
|
val := &QQMusicProvider{
|
||||||
cfg: ApiConfig{
|
cfg: ApiConfig{
|
||||||
Version: "13.2.5.8",
|
Version: "13.2.5.8",
|
||||||
@@ -58,6 +62,7 @@ func NewQQMusicProvider() *QQMusicProvider {
|
|||||||
},
|
},
|
||||||
qimeiUpdated: false,
|
qimeiUpdated: false,
|
||||||
tokenRefreshed: false,
|
tokenRefreshed: false,
|
||||||
|
channel: channel,
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ this part of code is inspired by
|
|||||||
|
|
||||||
- https://github.com/fred913/goqrcdec under Apache License
|
- https://github.com/fred913/goqrcdec under Apache License
|
||||||
|
|
||||||
|
qr login code is piece of shit. trying to find better way to do this
|
||||||
@@ -46,6 +46,12 @@ func (p *QQMusicProvider) makeApiRequest(module, method string, params map[strin
|
|||||||
|
|
||||||
cookie := map[string]interface{}{}
|
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() {
|
if p.cred.HasMusicKey() && p.cred.HasMusicID() {
|
||||||
common["authst"] = p.cred.MusicKey
|
common["authst"] = p.cred.MusicKey
|
||||||
common["qq"] = p.cred.MusicID
|
common["qq"] = p.cred.MusicID
|
||||||
@@ -98,7 +104,7 @@ func (p *QQMusicProvider) makeApiRequest(module, method string, params map[strin
|
|||||||
return gjson.Result{}, err
|
return gjson.Result{}, err
|
||||||
}
|
}
|
||||||
jsonResp := gjson.ParseBytes(response.Body())
|
jsonResp := gjson.ParseBytes(response.Body())
|
||||||
//fmt.Println(response.String())
|
//pp.Println(response.String())
|
||||||
moduleKeyEscaped := strings.ReplaceAll(moduleKey, ".", "\\.")
|
moduleKeyEscaped := strings.ReplaceAll(moduleKey, ".", "\\.")
|
||||||
if !jsonResp.Get(moduleKeyEscaped).Exists() {
|
if !jsonResp.Get(moduleKeyEscaped).Exists() {
|
||||||
return gjson.Result{}, fmt.Errorf("miaosic (qq): api request fail")
|
return gjson.Result{}, fmt.Errorf("miaosic (qq): api request fail")
|
||||||
|
|||||||
Reference in New Issue
Block a user