* feat: 支持返回登录二维码与 Docker 部署 * feat: 完善扫码登录功能 * fix: 修复当存在已经登录的情况,上层还会启动 goroutine的问题,并把 mcp 的返回增加为图片格式
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
package xiaohongshu
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/go-rod/rod"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type LoginAction struct {
|
|
page *rod.Page
|
|
}
|
|
|
|
func NewLogin(page *rod.Page) *LoginAction {
|
|
return &LoginAction{page: page}
|
|
}
|
|
|
|
func (a *LoginAction) CheckLoginStatus(ctx context.Context) (bool, error) {
|
|
pp := a.page.Context(ctx)
|
|
pp.MustNavigate("https://www.xiaohongshu.com/explore").MustWaitLoad()
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
exists, _, err := pp.Has(`.main-container .user .link-wrapper .channel`)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "check login status failed")
|
|
}
|
|
|
|
if !exists {
|
|
return false, errors.Wrap(err, "login status element not found")
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (a *LoginAction) Login(ctx context.Context) error {
|
|
pp := a.page.Context(ctx)
|
|
|
|
// 导航到小红书首页,这会触发二维码弹窗
|
|
pp.MustNavigate("https://www.xiaohongshu.com/explore").MustWaitLoad()
|
|
|
|
// 等待一小段时间让页面完全加载
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// 检查是否已经登录
|
|
if exists, _, _ := pp.Has(".main-container .user .link-wrapper .channel"); exists {
|
|
// 已经登录,直接返回
|
|
return nil
|
|
}
|
|
|
|
// 等待扫码成功提示或者登录完成
|
|
// 这里我们等待登录成功的元素出现,这样更简单可靠
|
|
pp.MustElement(".main-container .user .link-wrapper .channel")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *LoginAction) FetchQrcodeImage(ctx context.Context) (string, bool, error) {
|
|
pp := a.page.Context(ctx)
|
|
|
|
// 导航到小红书首页,这会触发二维码弹窗
|
|
pp.MustNavigate("https://www.xiaohongshu.com/explore").MustWaitLoad()
|
|
|
|
// 等待一小段时间让页面完全加载
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// 检查是否已经登录
|
|
if exists, _, _ := pp.Has(".main-container .user .link-wrapper .channel"); exists {
|
|
return "", true, nil
|
|
}
|
|
|
|
// 获取二维码图片
|
|
src, err := pp.MustElement(".login-container .qrcode-img").Attribute("src")
|
|
if err != nil {
|
|
return "", false, errors.Wrap(err, "get qrcode src failed")
|
|
}
|
|
if src == nil || len(*src) == 0 {
|
|
return "", false, errors.New("qrcode src is empty")
|
|
}
|
|
|
|
return *src, false, nil
|
|
}
|
|
|
|
func (a *LoginAction) WaitForLogin(ctx context.Context) bool {
|
|
pp := a.page.Context(ctx)
|
|
ticker := time.NewTicker(500 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return false
|
|
case <-ticker.C:
|
|
el, err := pp.Element(".main-container .user .link-wrapper .channel")
|
|
if err == nil && el != nil {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|