Files
xiaohongshu-mcp/xiaohongshu/comment_feed.go
2025-12-04 01:10:40 +08:00

203 lines
5.8 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 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)
}