package xiaohongshu import ( "context" "fmt" "time" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" "github.com/sirupsen/logrus" ) // CommentFeedAction 表示 Feed 评论动作 type CommentFeedAction struct { page *rod.Page } // NewCommentFeedAction 创建 Feed 评论动作 func NewCommentFeedAction(page *rod.Page) *CommentFeedAction { return &CommentFeedAction{page: page} } // PostComment 发表评论到 Feed func (f *CommentFeedAction) PostComment(ctx context.Context, feedID, xsecToken, content string) error { page := f.page.Context(ctx).Timeout(60 * time.Second) url := makeFeedDetailURL(feedID, xsecToken) logrus.Infof("打开 feed 详情页: %s", url) // 导航到详情页 page.MustNavigate(url) page.MustWaitDOMStable() time.Sleep(1 * time.Second) // 检测页面是否可访问 if err := checkPageAccessible(page); err != nil { return err } elem, err := page.Element("div.input-box div.content-edit span") if err != nil { logrus.Warnf("Failed to find comment input box: %v", err) return fmt.Errorf("未找到评论输入框,该帖子可能不支持评论或网页端不可访问: %w", err) } if err := elem.Click(proto.InputMouseButtonLeft, 1); err != nil { logrus.Warnf("Failed to click comment input box: %v", err) return fmt.Errorf("无法点击评论输入框: %w", err) } elem2, err := page.Element("div.input-box div.content-edit p.content-input") if err != nil { logrus.Warnf("Failed to find comment input field: %v", err) return fmt.Errorf("未找到评论输入区域: %w", err) } if err := elem2.Input(content); err != nil { logrus.Warnf("Failed to input comment content: %v", err) return fmt.Errorf("无法输入评论内容: %w", err) } time.Sleep(1 * time.Second) submitButton, err := page.Element("div.bottom button.submit") if err != nil { logrus.Warnf("Failed to find submit button: %v", err) return fmt.Errorf("未找到提交按钮: %w", err) } if err := submitButton.Click(proto.InputMouseButtonLeft, 1); err != nil { logrus.Warnf("Failed to click submit button: %v", err) return fmt.Errorf("无法点击提交按钮: %w", err) } time.Sleep(1 * time.Second) logrus.Infof("Comment posted successfully to feed: %s", feedID) return nil } // ReplyToComment 回复指定评论 func (f *CommentFeedAction) ReplyToComment(ctx context.Context, feedID, xsecToken, commentID, userID, content string) error { // 增加超时时间,因为需要滚动查找评论 page := f.page.Context(ctx).Timeout(5 * time.Minute) url := makeFeedDetailURL(feedID, xsecToken) logrus.Infof("打开 feed 详情页进行回复: %s", url) // 导航到详情页 page.MustNavigate(url) page.MustWaitDOMStable() time.Sleep(1 * time.Second) // 检测页面是否可访问 if err := checkPageAccessible(page); err != nil { return err } // 等待评论容器加载 time.Sleep(2 * time.Second) // 使用 Go 实现的查找逻辑 commentEl, err := findCommentElement(page, commentID, userID) if err != nil { return fmt.Errorf("无法找到评论: %w", err) } // 滚动到评论位置 logrus.Info("滚动到评论位置...") commentEl.MustScrollIntoView() time.Sleep(1 * time.Second) logrus.Info("准备点击回复按钮") // 查找并点击回复按钮 replyBtn, err := commentEl.Element(".right .interactions .reply") if err != nil { return fmt.Errorf("无法找到回复按钮: %w", err) } if err := replyBtn.Click(proto.InputMouseButtonLeft, 1); err != nil { return fmt.Errorf("点击回复按钮失败: %w", err) } time.Sleep(1 * time.Second) // 查找回复输入框 inputEl, err := page.Element("div.input-box div.content-edit p.content-input") if err != nil { return fmt.Errorf("无法找到回复输入框: %w", err) } // 输入内容 if err := inputEl.Input(content); err != nil { return fmt.Errorf("输入回复内容失败: %w", err) } time.Sleep(500 * time.Millisecond) // 查找并点击提交按钮 submitBtn, err := page.Element("div.bottom button.submit") if err != nil { return fmt.Errorf("无法找到提交按钮: %w", err) } if err := submitBtn.Click(proto.InputMouseButtonLeft, 1); err != nil { return fmt.Errorf("点击提交按钮失败: %w", err) } time.Sleep(2 * time.Second) logrus.Infof("回复评论成功") return nil } // findCommentElement 查找指定评论元素(Go 实现,减少 JS 代码) func findCommentElement(page *rod.Page, commentID, userID string) (*rod.Element, error) { logrus.Infof("开始查找评论 - commentID: %s, userID: %s", commentID, userID) const maxAttempts = 50 const scrollInterval = 800 * time.Millisecond // 先滚动到评论区 page.MustEval(`() => { const container = document.querySelector('.comments-container'); if (container) { container.scrollIntoView({behavior: 'smooth', block: 'start'}); } }`) time.Sleep(1 * time.Second) for attempt := 0; attempt < maxAttempts; attempt++ { logrus.Debugf("查找尝试 %d/%d", attempt+1, maxAttempts) // 优先通过 commentID 查找 if commentID != "" { selector := fmt.Sprintf("#comment-%s", commentID) if el, err := page.Element(selector); err == nil { logrus.Infof("✓ 通过 commentID 找到评论: %s", commentID) return el, nil } } // 通过 userID 查找 if userID != "" { elements, err := page.Elements(".comment-item, .comment") if err == nil { for _, el := range elements { userEl, err := el.Element(fmt.Sprintf(`[data-user-id="%s"]`, userID)) if err == nil && userEl != nil { logrus.Infof("✓ 通过 userID 找到评论: %s", userID) return el, nil } } } } // 滚动页面 page.MustEval(`() => window.scrollBy(0, window.innerHeight * 0.8)`) time.Sleep(scrollInterval) } return nil, fmt.Errorf("未找到评论 (commentID: %s, userID: %s), 尝试次数: %d", commentID, userID, maxAttempts) }