diff --git a/README.md b/README.md index ed0300c..a718916 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,35 @@ https://github.com/user-attachments/assets/cc385b6c-422c-489b-a5fc-63e92c695b80 ## 1. 使用教程 -### 1.1. 登录 +### 1.1. 安装 + +
+安装配置详情 + +依赖 Golang 环境,安装方法请参考 [Golang 官方文档](https://go.dev/doc/install)。 + +设置 Go 国内源的代理, + +```bash +# 配置 GOPROXY 环境变量,以下三选一 + +# 1. 七牛 CDN +go env -w GOPROXY=https://goproxy.cn,direct + +# 2. 阿里云 +go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct + +# 3. 官方 +go env -w GOPROXY=https://goproxy.io,direct +``` + +
+ +Windows 问题: + +可以参考这里 https://github.com/xpzouying/xiaohongshu-mcp/issues/56 + +### 1.2. 登录 第一次需要手动登录,需要保存小红书的登录状态。 @@ -145,7 +173,7 @@ https://github.com/user-attachments/assets/cc385b6c-422c-489b-a5fc-63e92c695b80 go run cmd/login/main.go ``` -### 1.2. 启动 MCP 服务 +### 1.3. 启动 MCP 服务 启动 xiaohongshu-mcp 服务。 @@ -158,7 +186,7 @@ go run . go run . -headless=false ``` -## 1.3. 验证 MCP +## 1.4. 验证 MCP ```bash npx @modelcontextprotocol/inspector @@ -172,7 +200,7 @@ npx @modelcontextprotocol/inspector 按照上面配置 MCP inspector 后,点击 `List Tools` 按钮,查看所有的 Tools。 -## 1.4. 使用 MCP 发布 +## 1.5. 使用 MCP 发布 ### 检查登录状态 @@ -410,7 +438,6 @@ npx @modelcontextprotocol/inspector xiaohongshu-mcp 发布结果 - ## 小红书 MCP 互助群 因为项目刚刚启动,会有很多问题,拉一个群大家一起讨论问题,一起为开源项目做贡献。扫我的微信二维码加群讨论技术。 @@ -418,4 +445,3 @@ npx @modelcontextprotocol/inspector **申请时务必添加备注。** ![WechatIMG111](https://github.com/user-attachments/assets/ae535953-6c8b-4c12-91b6-ac1572ce9032) - diff --git a/browser/browser.go b/browser/browser.go index 0cd380b..4da1392 100644 --- a/browser/browser.go +++ b/browser/browser.go @@ -6,11 +6,30 @@ import ( "github.com/xpzouying/xiaohongshu-mcp/cookies" ) -func NewBrowser(headless bool) *headless_browser.Browser { +type browserConfig struct { + binPath string +} + +type Option func(*browserConfig) + +func WithBinPath(binPath string) Option { + return func(c *browserConfig) { + c.binPath = binPath + } +} + +func NewBrowser(headless bool, options ...Option) *headless_browser.Browser { + cfg := &browserConfig{} + for _, opt := range options { + opt(cfg) + } opts := []headless_browser.Option{ headless_browser.WithHeadless(headless), } + if cfg.binPath != "" { + opts = append(opts, headless_browser.WithChromeBinPath(cfg.binPath)) + } // 加载 cookies cookiePath := cookies.GetCookiesFilePath() diff --git a/cmd/login/main.go b/cmd/login/main.go index a096203..5a3fd96 100644 --- a/cmd/login/main.go +++ b/cmd/login/main.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "flag" "github.com/go-rod/rod" "github.com/sirupsen/logrus" @@ -12,9 +13,14 @@ import ( ) func main() { + var ( + binPath string // 浏览器二进制文件路径 + ) + flag.StringVar(&binPath, "bin", "", "浏览器二进制文件路径") + flag.Parse() // 登录的时候,需要界面,所以不能无头模式 - b := browser.NewBrowser(false) + b := browser.NewBrowser(false, browser.WithBinPath(binPath)) defer b.Close() page := b.NewPage() diff --git a/configs/browser.go b/configs/browser.go index 0e80071..34f4538 100644 --- a/configs/browser.go +++ b/configs/browser.go @@ -2,6 +2,8 @@ package configs var ( useHeadless = true + + binPath = "" ) func InitHeadless(h bool) { @@ -12,3 +14,11 @@ func InitHeadless(h bool) { func IsHeadless() bool { return useHeadless } + +func SetBinPath(b string) { + binPath = b +} + +func GetBinPath() string { + return binPath +} diff --git a/go.mod b/go.mod index 56b140d..a1bf9fd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/xpzouying/xiaohongshu-mcp -go 1.23.5 +go 1.24.0 require ( github.com/gin-gonic/gin v1.10.1 @@ -9,7 +9,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 - github.com/xpzouying/headless_browser v0.0.2 + github.com/xpzouying/headless_browser v0.1.0 ) require ( @@ -45,7 +45,7 @@ require ( golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 723e0a7..6aa5250 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,12 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/xpzouying/headless_browser v0.0.2 h1:sLc4gqUT/5IyTruYIOfCW4aZLinq38hIdUHCHem1KYo= github.com/xpzouying/headless_browser v0.0.2/go.mod h1:bQTSzGYHIipa1zwToMlOGHcXWDlvw8y33Cx5zzElekc= +github.com/xpzouying/headless_browser v0.1.0 h1:0FyMIzhe/If/VhEdDrs7T1fqm1gOZSCFrmMXI/1JM58= +github.com/xpzouying/headless_browser v0.1.0/go.mod h1:bQTSzGYHIipa1zwToMlOGHcXWDlvw8y33Cx5zzElekc= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= +github.com/ysmood/fetchup v0.5.2 h1:P9w3OIA7RSNEEFvEmOiTq09IOu42C96PMyZ1MWd8TAs= +github.com/ysmood/fetchup v0.5.2/go.mod h1:yCv8s8itjsCul1LGXJ1Q+8EQnZcVjfbZ4+l1zDm4StE= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk= @@ -110,6 +114,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/main.go b/main.go index 914c636..7c5e07b 100644 --- a/main.go +++ b/main.go @@ -10,11 +10,14 @@ import ( func main() { var ( headless bool + binPath string // 浏览器二进制文件路径 ) flag.BoolVar(&headless, "headless", true, "是否无头模式") + flag.StringVar(&binPath, "bin", "", "浏览器二进制文件路径") flag.Parse() configs.InitHeadless(headless) + configs.SetBinPath(binPath) // 初始化服务 xiaohongshuService := NewXiaohongshuService() diff --git a/service.go b/service.go index d55969a..d8d8589 100644 --- a/service.go +++ b/service.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/mattn/go-runewidth" + "github.com/xpzouying/headless_browser" "github.com/xpzouying/xiaohongshu-mcp/browser" "github.com/xpzouying/xiaohongshu-mcp/configs" "github.com/xpzouying/xiaohongshu-mcp/pkg/downloader" @@ -50,7 +51,7 @@ type FeedsListResponse struct { // CheckLoginStatus 检查登录状态 func (s *XiaohongshuService) CheckLoginStatus(ctx context.Context) (*LoginStatusResponse, error) { - b := browser.NewBrowser(configs.IsHeadless()) + b := newBrowser() defer b.Close() page := b.NewPage() @@ -117,7 +118,7 @@ func (s *XiaohongshuService) processImages(images []string) ([]string, error) { // publishContent 执行内容发布 func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohongshu.PublishImageContent) error { - b := browser.NewBrowser(configs.IsHeadless()) + b := newBrowser() defer b.Close() page := b.NewPage() @@ -134,7 +135,7 @@ func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohon // ListFeeds 获取Feeds列表 func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, error) { - b := browser.NewBrowser(configs.IsHeadless()) + b := newBrowser() defer b.Close() page := b.NewPage() @@ -158,7 +159,7 @@ func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, } func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (*FeedsListResponse, error) { - b := browser.NewBrowser(configs.IsHeadless()) + b := newBrowser() defer b.Close() page := b.NewPage() @@ -181,7 +182,7 @@ func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (* // GetFeedDetail 获取Feed详情 func (s *XiaohongshuService) GetFeedDetail(ctx context.Context, feedID, xsecToken string) (*FeedDetailResponse, error) { - b := browser.NewBrowser(configs.IsHeadless()) + b := newBrowser() defer b.Close() page := b.NewPage() @@ -207,7 +208,7 @@ func (s *XiaohongshuService) GetFeedDetail(ctx context.Context, feedID, xsecToke // PostCommentToFeed 发表评论到Feed func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsecToken, content string) (*PostCommentResponse, error) { // 使用非无头模式以便查看操作过程 - b := browser.NewBrowser(false) + b := newBrowser() defer b.Close() page := b.NewPage() @@ -230,3 +231,7 @@ func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsec return response, nil } + +func newBrowser() *headless_browser.Browser { + return browser.NewBrowser(configs.IsHeadless(), browser.WithBinPath(configs.GetBinPath())) +}