From aee7b321d3aa6301bef99bbf759d40ddfc3780c8 Mon Sep 17 00:00:00 2001 From: zy Date: Tue, 12 Aug 2025 01:22:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=B0=8F=E7=BA=A2=E4=B9=A6=E9=A6=96=E9=A1=B5=20Feeds=20?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9A=84=20HTTP=20=E5=92=8C=20MCP=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 service.go 中添加 ListFeeds 业务逻辑,复用 xiaohongshu 包功能 - 添加 HTTP 接口 GET /api/v1/feeds/list - 添加 MCP tool: list_feeds,支持通过 MCP 协议获取 Feeds - 返回结构化的 Feeds 数据,包含列表和数量统计 - 更新 .gitignore 忽略构建产物和测试脚本 - 更新项目配置,添加 chmod 权限 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 3 ++- .gitignore | 6 +++++ CLAUDE.md | 3 ++- handlers.go | 14 ++++++++++++ mcp_server.go | 45 +++++++++++++++++++++++++++++++++++++ server.go | 3 +++ service.go | 29 ++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a8f5e69..2d4c349 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,8 @@ "Bash(rmdir:*)", "Bash(rm:*)", "Bash(gofmt:*)", - "Bash(goimports:*)" + "Bash(goimports:*)", + "Bash(chmod:*)" ], "deny": [] } diff --git a/.gitignore b/.gitignore index f14d158..21b7dd2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,9 @@ go.work.sum # .idea/ # .vscode/ # .claude/ + +# Build artifacts +xiaohongshu-mcp + +# Test scripts +test_*.sh diff --git a/CLAUDE.md b/CLAUDE.md index 37ad77d..04d7161 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,2 @@ -- 要求每次修改完后,需要帮我格式化 Go 源码文件. \ No newline at end of file +- 要求每次修改完后,需要帮我格式化 Go 源码文件. +- 测试过程中产生的脚本和build中间文件,如果没有必要,则删除. \ No newline at end of file diff --git a/handlers.go b/handlers.go index a98cf6e..947865a 100644 --- a/handlers.go +++ b/handlers.go @@ -90,6 +90,20 @@ func publishHandler(c *gin.Context) { respondSuccess(c, result, "发布成功") } +// listFeedsHandler 获取Feeds列表 +func listFeedsHandler(c *gin.Context) { + // 获取 Feeds 列表 + result, err := xiaohongshuService.ListFeeds(c.Request.Context()) + if err != nil { + respondError(c, http.StatusInternalServerError, "LIST_FEEDS_FAILED", + "获取Feeds列表失败", err.Error()) + return + } + + c.Set("account", "ai-report") + respondSuccess(c, result, "获取Feeds列表成功") +} + // healthHandler 健康检查 func healthHandler(c *gin.Context) { respondSuccess(c, map[string]any{ diff --git a/mcp_server.go b/mcp_server.go index c4648be..43ac34f 100644 --- a/mcp_server.go +++ b/mcp_server.go @@ -118,6 +118,41 @@ func handlePublishContent(ctx context.Context, args map[string]interface{}) *MCP } } +// handleListFeeds 处理获取Feeds列表 +func handleListFeeds(ctx context.Context) *MCPToolResult { + logrus.Info("MCP: 获取Feeds列表") + + result, err := xiaohongshuService.ListFeeds(ctx) + if err != nil { + return &MCPToolResult{ + Content: []MCPContent{{ + Type: "text", + Text: "获取Feeds列表失败: " + err.Error(), + }}, + IsError: true, + } + } + + // 格式化输出,转换为JSON字符串 + jsonData, err := json.MarshalIndent(result, "", " ") + if err != nil { + return &MCPToolResult{ + Content: []MCPContent{{ + Type: "text", + Text: fmt.Sprintf("获取Feeds列表成功,但序列化失败: %v", err), + }}, + IsError: true, + } + } + + return &MCPToolResult{ + Content: []MCPContent{{ + Type: "text", + Text: string(jsonData), + }}, + } +} + // handleMCPRequest 处理 MCP 请求 func handleMCPRequest(w http.ResponseWriter, r *http.Request) { var req JSONRPCRequest @@ -193,6 +228,14 @@ func handleToolsList(w http.ResponseWriter, req JSONRPCRequest) { "required": []string{"title", "content", "images"}, }, }, + { + "name": "list_feeds", + "description": "获取小红书首页Feeds列表", + "inputSchema": map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{}, + }, + }, } result := map[string]interface{}{ @@ -220,6 +263,8 @@ func handleToolsCall(w http.ResponseWriter, r *http.Request, req JSONRPCRequest) result = handleCheckLoginStatus(ctx) case "publish_content": result = handlePublishContent(ctx, toolCall.Arguments) + case "list_feeds": + result = handleListFeeds(ctx) default: logrus.Warnf("不支持的工具: %s", toolCall.Name) sendJSONRPCError(w, req.ID, -32601, "Tool not found", nil) diff --git a/server.go b/server.go index b9d9e00..f1e98e2 100644 --- a/server.go +++ b/server.go @@ -40,6 +40,9 @@ func setupRouter() *gin.Engine { api.GET("/login/status", checkLoginStatusHandler) api.POST("/publish", publishHandler) + + // Feeds 相关路由 + api.GET("/feeds/list", listFeedsHandler) } return router diff --git a/service.go b/service.go index b2cb52a..104dd53 100644 --- a/service.go +++ b/service.go @@ -39,6 +39,12 @@ type PublishResponse struct { PostID string `json:"post_id,omitempty"` } +// FeedsListResponse Feeds列表响应 +type FeedsListResponse struct { + Feeds []xiaohongshu.Feed `json:"feeds"` + Count int `json:"count"` +} + // CheckLoginStatus 检查登录状态 func (s *XiaohongshuService) CheckLoginStatus(ctx context.Context) (*LoginStatusResponse, error) { // 使用全局单例浏览器创建新页面 @@ -109,3 +115,26 @@ func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohon // 执行发布 return action.Publish(ctx, content) } + +// ListFeeds 获取Feeds列表 +func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, error) { + // 使用全局单例浏览器创建新页面 + page := browser.NewPage() + defer page.Close() + + // 创建 Feeds 列表 action + action := xiaohongshu.NewFeedsListAction(page) + + // 获取 Feeds 列表 + feeds, err := action.GetFeedsList(ctx) + if err != nil { + return nil, err + } + + response := &FeedsListResponse{ + Feeds: feeds, + Count: len(feeds), + } + + return response, nil +}