* 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>
276 lines
8.0 KiB
Go
276 lines
8.0 KiB
Go
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}, "获取我的主页成功")
|
||
}
|