feat: 支持返回登录二维码与 Docker 部署 (#155)

* feat: 支持返回登录二维码与 Docker 部署

* feat: 完善扫码登录功能

* fix: 修复当存在已经登录的情况,上层还会启动 goroutine的问题,并把 mcp 的返回增加为图片格式
This commit is contained in:
lmxdawn
2025-09-25 19:44:01 +08:00
committed by GitHub
parent cc5038decd
commit a8a2743a51
14 changed files with 314 additions and 5 deletions

View File

@@ -2,14 +2,18 @@ package main
import (
"context"
"encoding/json"
"fmt"
"github.com/go-rod/rod"
"github.com/mattn/go-runewidth"
"github.com/sirupsen/logrus"
"github.com/xpzouying/headless_browser"
"github.com/xpzouying/xiaohongshu-mcp/browser"
"github.com/xpzouying/xiaohongshu-mcp/configs"
"github.com/xpzouying/xiaohongshu-mcp/cookies"
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
"time"
)
// XiaohongshuService 小红书业务服务
@@ -34,6 +38,13 @@ type LoginStatusResponse struct {
Username string `json:"username,omitempty"`
}
// LoginQrcodeResponse 登录扫码二维码
type LoginQrcodeResponse struct {
Timeout string `json:"timeout"`
IsLoggedIn bool `json:"is_logged_in"`
Img string `json:"img,omitempty"`
}
// PublishResponse 发布响应
type PublishResponse struct {
Title string `json:"title"`
@@ -79,6 +90,54 @@ func (s *XiaohongshuService) CheckLoginStatus(ctx context.Context) (*LoginStatus
return response, nil
}
// GetLoginQrcode 获取登录的扫码二维码
func (s *XiaohongshuService) GetLoginQrcode(ctx context.Context) (*LoginQrcodeResponse, error) {
b := newBrowser()
page := b.NewPage()
deferFunc := func() {
_ = page.Close()
b.Close()
}
loginAction := xiaohongshu.NewLogin(page)
img, loggedIn, err := loginAction.FetchQrcodeImage(ctx)
if err != nil || loggedIn {
defer deferFunc()
}
if err != nil {
return nil, err
}
timeout := 4 * time.Minute
if !loggedIn {
go func() {
ctxTimeout, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
defer deferFunc()
if loginAction.WaitForLogin(ctxTimeout) {
if er := saveCookies(page); er != nil {
logrus.Errorf("failed to save cookies: %v", er)
}
}
}()
}
return &LoginQrcodeResponse{
Timeout: func() string {
if loggedIn {
return "0s"
}
return timeout.String()
}(),
Img: img,
IsLoggedIn: loggedIn,
}, nil
}
// PublishContent 发布内容
func (s *XiaohongshuService) PublishContent(ctx context.Context, req *PublishRequest) (*PublishResponse, error) {
// 验证标题长度
@@ -266,3 +325,18 @@ func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsec
func newBrowser() *headless_browser.Browser {
return browser.NewBrowser(configs.IsHeadless(), browser.WithBinPath(configs.GetBinPath()))
}
func saveCookies(page *rod.Page) error {
cks, err := page.Browser().GetCookies()
if err != nil {
return err
}
data, err := json.Marshal(cks)
if err != nil {
return err
}
cookieLoader := cookies.NewLoadCookie(cookies.GetCookiesFilePath())
return cookieLoader.SaveCookies(data)
}