Files
xiaohongshu-mcp/xiaohongshu/feed_detail.go
zy cd28e4064e feat: 添加小红书Feed评论功能 (#50)
实现通过HTTP GIN API和MCP API发表评论到小红书Feed的功能:
- 新增POST /api/v1/feeds/comment端点
- 新增post_comment_to_feed MCP工具
- 添加PostCommentRequest和PostCommentResponse类型
- 实现PostCommentToFeed服务方法
- 新增CommentFeedAction用于浏览器自动化操作

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-09 23:13:05 +08:00

82 lines
2.1 KiB
Go

package xiaohongshu
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
"github.com/go-rod/rod"
)
// FeedDetailAction 表示 Feed 详情页动作
type FeedDetailAction struct {
page *rod.Page
}
// NewFeedDetailAction 创建 Feed 详情页动作
func NewFeedDetailAction(page *rod.Page) *FeedDetailAction {
return &FeedDetailAction{page: page}
}
// GetFeedDetail 获取 Feed 详情页数据
func (f *FeedDetailAction) GetFeedDetail(ctx context.Context, feedID, xsecToken string) (*FeedDetailResponse, error) {
page := f.page.Context(ctx).Timeout(60 * time.Second)
// 构建详情页 URL
url := makeFeedDetailURL(feedID, xsecToken)
// 导航到详情页
page.MustNavigate(url)
page.MustWaitStable()
page.MustWait(`() => window.__INITIAL_STATE__ !== undefined`)
// 获取 window.__INITIAL_STATE__ 并转换为 JSON 字符串
result := page.MustEval(`() => {
if (window.__INITIAL_STATE__) {
return JSON.stringify(window.__INITIAL_STATE__);
}
return "";
}`).String()
if result == "" {
return nil, fmt.Errorf("__INITIAL_STATE__ not found")
}
// 将原始结果保存到 feed_detail.json 文件用于测试
err := os.WriteFile("feed_detail.json", []byte(result), 0644)
if err != nil {
return nil, fmt.Errorf("failed to write feed_detail.json: %w", err)
}
// 定义响应结构并直接反序列化
var initialState struct {
Note struct {
NoteDetailMap map[string]struct {
Note FeedDetail `json:"note"`
Comments CommentList `json:"comments"`
} `json:"noteDetailMap"`
} `json:"note"`
}
if err := json.Unmarshal([]byte(result), &initialState); err != nil {
return nil, fmt.Errorf("failed to unmarshal __INITIAL_STATE__: %w", err)
}
// 从 noteDetailMap 中获取对应 feedID 的数据
noteDetail, exists := initialState.Note.NoteDetailMap[feedID]
if !exists {
return nil, fmt.Errorf("feed %s not found in noteDetailMap", feedID)
}
return &FeedDetailResponse{
Note: noteDetail.Note,
Comments: noteDetail.Comments,
}, nil
}
func makeFeedDetailURL(feedID, xsecToken string) string {
return fmt.Sprintf("https://www.xiaohongshu.com/explore/%s?xsec_token=%s&xsec_source=pc_feed", feedID, xsecToken)
}