Files
xiaohongshu-mcp/service.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

222 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"context"
"github.com/xpzouying/xiaohongshu-mcp/browser"
"github.com/xpzouying/xiaohongshu-mcp/configs"
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
)
// XiaohongshuService 小红书业务服务
type XiaohongshuService struct{}
// NewXiaohongshuService 创建小红书服务实例
func NewXiaohongshuService() *XiaohongshuService {
return &XiaohongshuService{}
}
// PublishRequest 发布请求
type PublishRequest struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Images []string `json:"images" binding:"required,min=1"`
}
// LoginStatusResponse 登录状态响应
type LoginStatusResponse struct {
IsLoggedIn bool `json:"is_logged_in"`
Username string `json:"username,omitempty"`
}
// PublishResponse 发布响应
type PublishResponse struct {
Title string `json:"title"`
Content string `json:"content"`
Images int `json:"images"`
Status string `json:"status"`
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) {
b := browser.NewBrowser(configs.IsHeadless())
defer b.Close()
page := b.NewPage()
defer page.Close()
loginAction := xiaohongshu.NewLogin(page)
isLoggedIn, err := loginAction.CheckLoginStatus(ctx)
if err != nil {
return nil, err
}
response := &LoginStatusResponse{
IsLoggedIn: isLoggedIn,
Username: configs.Username,
}
return response, nil
}
// PublishContent 发布内容
func (s *XiaohongshuService) PublishContent(ctx context.Context, req *PublishRequest) (*PublishResponse, error) {
// 处理图片下载URL图片或使用本地路径
imagePaths, err := s.processImages(req.Images)
if err != nil {
return nil, err
}
// 构建发布内容
content := xiaohongshu.PublishImageContent{
Title: req.Title,
Content: req.Content,
ImagePaths: imagePaths,
}
// 执行发布
if err := s.publishContent(ctx, content); err != nil {
return nil, err
}
response := &PublishResponse{
Title: req.Title,
Content: req.Content,
Images: len(imagePaths),
Status: "发布完成",
}
return response, nil
}
// processImages 处理图片列表支持URL下载和本地路径
func (s *XiaohongshuService) processImages(images []string) ([]string, error) {
processor := downloader.NewImageProcessor()
return processor.ProcessImages(images)
}
// publishContent 执行内容发布
func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohongshu.PublishImageContent) error {
b := browser.NewBrowser(configs.IsHeadless())
defer b.Close()
page := b.NewPage()
defer page.Close()
action, err := xiaohongshu.NewPublishImageAction(page)
if err != nil {
return err
}
// 执行发布
return action.Publish(ctx, content)
}
// ListFeeds 获取Feeds列表
func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, error) {
b := browser.NewBrowser(configs.IsHeadless())
defer b.Close()
page := b.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
}
func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (*FeedsListResponse, error) {
b := browser.NewBrowser(configs.IsHeadless())
defer b.Close()
page := b.NewPage()
defer page.Close()
action := xiaohongshu.NewSearchAction(page)
feeds, err := action.Search(ctx, keyword)
if err != nil {
return nil, err
}
response := &FeedsListResponse{
Feeds: feeds,
Count: len(feeds),
}
return response, nil
}
// GetFeedDetail 获取Feed详情
func (s *XiaohongshuService) GetFeedDetail(ctx context.Context, feedID, xsecToken string) (*FeedDetailResponse, error) {
b := browser.NewBrowser(configs.IsHeadless())
defer b.Close()
page := b.NewPage()
defer page.Close()
// 创建 Feed 详情 action
action := xiaohongshu.NewFeedDetailAction(page)
// 获取 Feed 详情
result, err := action.GetFeedDetail(ctx, feedID, xsecToken)
if err != nil {
return nil, err
}
response := &FeedDetailResponse{
FeedID: feedID,
Data: result,
}
return response, nil
}
// PostCommentToFeed 发表评论到Feed
func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsecToken, content string) (*PostCommentResponse, error) {
// 使用非无头模式以便查看操作过程
b := browser.NewBrowser(false)
defer b.Close()
page := b.NewPage()
defer page.Close()
// 创建 Feed 评论 action
action := xiaohongshu.NewCommentFeedAction(page)
// 发表评论
err := action.PostComment(ctx, feedID, xsecToken, content)
if err != nil {
return nil, err
}
response := &PostCommentResponse{
FeedID: feedID,
Success: true,
Message: "评论发表成功",
}
return response, nil
}