Files
xiaohongshu-mcp/handlers_api.go
haikow 13ac2e39c3 修复完善笔记详情内容加载 (#301)
* feat: add like and favorite functionality for feeds

- Implemented handleLikeFeed and handleFavoriteFeed methods in mcp_handlers.go to manage liking and favoriting feeds.
- Added LikeFavoriteArgs struct in mcp_server.go for handling parameters.
- Registered new MCP tools for liking and favoriting feeds in registerTools function.
- Introduced LikeFeed and FavoriteFeed methods in XiaohongshuService to interact with the respective actions.
- Created LikeFavoriteAction in a new file to encapsulate the logic for liking and favoriting feeds on the Xiaohongshu platform.

* "优化评论反馈逻辑:简化回复按钮查找和点击流程

* "Fix-build-errors"

* refactor: streamline like and favorite actions in LikeFavoriteAction

- Introduced a generic method `performInteractAction` to handle both liking and favoriting feeds, reducing code duplication.
- Updated logging to reflect the action type being performed (like or favorite).
- Enhanced state verification after interaction to ensure accurate feedback on success or failure.
- Removed the `clickLastMatch` function, simplifying the interaction logic.

* "Add-unlike-and-unfavorite-functionality"

* "Refactor-performInteractAction-function"

* "Refactor-split-LikeFavoriteAction-into-separate-actions"

* "Add-content-length-validation-for-publish"

* refactor: improve comment posting logic with enhanced error handling and stability checks

- Updated the PostComment method to include error handling for navigation and element interactions.
- Replaced sleep calls with more reliable wait mechanisms to ensure page stability.
- Added checks for the presence of input elements and improved logging for better debugging.

* feat: add reply comment functionality for Xiaohongshu feeds

- Implemented handleReplyComment method in mcp_handlers.go to manage replying to comments on feeds.
- Introduced ReplyCommentArgs struct in mcp_server.go for handling parameters related to comment replies.
- Registered a new MCP tool for replying to comments in the registerTools function.
- Added ReplyCommentToFeed method in service.go to interact with the Xiaohongshu platform for comment replies.
- Enhanced error handling for missing parameters in the reply process.

* refactor: enhance reply comment functionality with improved error handling and response structure

- Simplified error handling in handleReplyComment to check for both comment_id and user_id simultaneously.
- Updated response message to include both Comment ID and User ID upon successful reply.
- Modified ReplyCommentArgs struct to make comment_id and user_id optional.
- Renamed MCP tool for replying to comments for clarity.

* feat(feed): Migrate loadAllComments feature for GetFeedDetail

* fix

* fix

* fix

* fix

* fix: 添加更多自定义选项操作

* fix

* fix:优化代码结构

* chore: update dependencies and implement retry logic for page interactions

---------

Co-authored-by: chekayo <9827969+chekayo@user.noreply.gitee.com>
2025-12-07 15:04:14 +08:00

276 lines
8.0 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 (
"net/http"
"github.com/xpzouying/xiaohongshu-mcp/cookies"
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// respondError 返回错误响应
func respondError(c *gin.Context, statusCode int, code, message string, details any) {
response := ErrorResponse{
Error: message,
Code: code,
Details: details,
}
logrus.Errorf("%s %s %s %d", c.Request.Method, c.Request.URL.Path,
c.GetString("account"), statusCode)
c.JSON(statusCode, response)
}
// respondSuccess 返回成功响应
func respondSuccess(c *gin.Context, data any, message string) {
response := SuccessResponse{
Success: true,
Data: data,
Message: message,
}
logrus.Infof("%s %s %s %d", c.Request.Method, c.Request.URL.Path,
c.GetString("account"), http.StatusOK)
c.JSON(http.StatusOK, response)
}
// checkLoginStatusHandler 检查登录状态
func (s *AppServer) checkLoginStatusHandler(c *gin.Context) {
status, err := s.xiaohongshuService.CheckLoginStatus(c.Request.Context())
if err != nil {
respondError(c, http.StatusInternalServerError, "STATUS_CHECK_FAILED",
"检查登录状态失败", err.Error())
return
}
c.Set("account", "ai-report")
respondSuccess(c, status, "检查登录状态成功")
}
// getLoginQrcodeHandler 处理 [GET /api/login/qrcode] 请求。
// 用于生成并返回登录二维码Base64 图片 + 超时时间),供前端展示给用户扫码登录。
func (s *AppServer) getLoginQrcodeHandler(c *gin.Context) {
result, err := s.xiaohongshuService.GetLoginQrcode(c.Request.Context())
if err != nil {
respondError(c, http.StatusInternalServerError, "STATUS_CHECK_FAILED",
"获取登录二维码失败", err.Error())
return
}
respondSuccess(c, result, "获取登录二维码成功")
}
// deleteCookiesHandler 删除 cookies重置登录状态
func (s *AppServer) deleteCookiesHandler(c *gin.Context) {
err := s.xiaohongshuService.DeleteCookies(c.Request.Context())
if err != nil {
respondError(c, http.StatusInternalServerError, "DELETE_COOKIES_FAILED",
"删除 cookies 失败", err.Error())
return
}
cookiePath := cookies.GetCookiesFilePath()
respondSuccess(c, map[string]interface{}{
"cookie_path": cookiePath,
"message": "Cookies 已成功删除,登录状态已重置。下次操作时需要重新登录。",
}, "删除 cookies 成功")
}
// publishHandler 发布内容
func (s *AppServer) publishHandler(c *gin.Context) {
var req PublishRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
"请求参数错误", err.Error())
return
}
// 执行发布
result, err := s.xiaohongshuService.PublishContent(c.Request.Context(), &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "PUBLISH_FAILED",
"发布失败", err.Error())
return
}
respondSuccess(c, result, "发布成功")
}
// publishVideoHandler 发布视频内容
func (s *AppServer) publishVideoHandler(c *gin.Context) {
var req PublishVideoRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
"请求参数错误", err.Error())
return
}
// 执行视频发布
result, err := s.xiaohongshuService.PublishVideo(c.Request.Context(), &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "PUBLISH_VIDEO_FAILED",
"视频发布失败", err.Error())
return
}
respondSuccess(c, result, "视频发布成功")
}
// listFeedsHandler 获取Feeds列表
func (s *AppServer) listFeedsHandler(c *gin.Context) {
// 获取 Feeds 列表
result, err := s.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列表成功")
}
// searchFeedsHandler 搜索Feeds
func (s *AppServer) searchFeedsHandler(c *gin.Context) {
var keyword string
var filters xiaohongshu.FilterOption
switch c.Request.Method {
case http.MethodPost:
// 对于POST请求从JSON中获取keyword
var searchReq SearchFeedsRequest
if err := c.ShouldBindJSON(&searchReq); err != nil {
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
"请求参数错误", err.Error())
return
}
keyword = searchReq.Keyword
filters = searchReq.Filters
default:
keyword = c.Query("keyword")
}
if keyword == "" {
respondError(c, http.StatusBadRequest, "MISSING_KEYWORD",
"缺少关键词参数", "keyword parameter is required")
return
}
// 搜索 Feeds
result, err := s.xiaohongshuService.SearchFeeds(c.Request.Context(), keyword, filters)
if err != nil {
respondError(c, http.StatusInternalServerError, "SEARCH_FEEDS_FAILED",
"搜索Feeds失败", err.Error())
return
}
c.Set("account", "ai-report")
respondSuccess(c, result, "搜索Feeds成功")
}
// getFeedDetailHandler 获取Feed详情
func (s *AppServer) getFeedDetailHandler(c *gin.Context) {
var req FeedDetailRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
"请求参数错误", err.Error())
return
}
var result *FeedDetailResponse
var err error
if req.CommentConfig != nil {
// 使用配置参数
config := xiaohongshu.CommentLoadConfig{
ClickMoreReplies: req.CommentConfig.ClickMoreReplies,
MaxRepliesThreshold: req.CommentConfig.MaxRepliesThreshold,
MaxCommentItems: req.CommentConfig.MaxCommentItems,
ScrollSpeed: req.CommentConfig.ScrollSpeed,
}
result, err = s.xiaohongshuService.GetFeedDetailWithConfig(c.Request.Context(), req.FeedID, req.XsecToken, req.LoadAllComments, config)
} else {
// 使用默认配置
result, err = s.xiaohongshuService.GetFeedDetail(c.Request.Context(), req.FeedID, req.XsecToken, req.LoadAllComments)
}
if err != nil {
respondError(c, http.StatusInternalServerError, "GET_FEED_DETAIL_FAILED",
"获取Feed详情失败", err.Error())
return
}
c.Set("account", "ai-report")
respondSuccess(c, result, "获取Feed详情成功")
}
// userProfileHandler 用户主页
func (s *AppServer) userProfileHandler(c *gin.Context) {
var req UserProfileRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
"请求参数错误", err.Error())
return
}
// 获取用户信息
result, err := s.xiaohongshuService.UserProfile(c.Request.Context(), req.UserID, req.XsecToken)
if err != nil {
respondError(c, http.StatusInternalServerError, "GET_USER_PROFILE_FAILED",
"获取用户主页失败", err.Error())
return
}
c.Set("account", "ai-report")
respondSuccess(c, map[string]any{"data": result}, "result.Message")
}
// postCommentHandler 发表评论到Feed
func (s *AppServer) postCommentHandler(c *gin.Context) {
var req PostCommentRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
"请求参数错误", err.Error())
return
}
// 发表评论
result, err := s.xiaohongshuService.PostCommentToFeed(c.Request.Context(), req.FeedID, req.XsecToken, req.Content)
if err != nil {
respondError(c, http.StatusInternalServerError, "POST_COMMENT_FAILED",
"发表评论失败", err.Error())
return
}
c.Set("account", "ai-report")
respondSuccess(c, result, result.Message)
}
// healthHandler 健康检查
func healthHandler(c *gin.Context) {
respondSuccess(c, map[string]any{
"status": "healthy",
"service": "xiaohongshu-mcp",
"account": "ai-report",
"timestamp": "now",
}, "服务正常")
}
// myProfileHandler 我的信息
func (s *AppServer) myProfileHandler(c *gin.Context) {
// 获取当前登录用户信息
result, err := s.xiaohongshuService.GetMyProfile(c.Request.Context())
if err != nil {
respondError(c, http.StatusInternalServerError, "GET_MY_PROFILE_FAILED",
"获取我的主页失败", err.Error())
return
}
c.Set("account", "ai-report")
respondSuccess(c, map[string]any{"data": result}, "获取我的主页成功")
}