From 10898374e2bede3e05d5a63206cfb16d4edd7200 Mon Sep 17 00:00:00 2001 From: tanjun Date: Fri, 6 Feb 2026 20:12:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=86=E5=8F=91=E5=B8=83=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E4=B8=AD=E7=9A=84=20Must*=20=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=BA=E9=94=99=E8=AF=AF=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=20context=20=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=97=B6=20panic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit publish_content 和 publish_with_video 工具在 MCP 客户端断开或超时时, rod 的 Must* 方法会因 context canceled 直接 panic。 将 inputTag、inputTags、submitPublish、submitPublishVideo 中的 Must* 调用 替换为带 error 返回的安全版本,使错误能正常传播而非 panic。 Closes #352 Co-Authored-By: Claude Opus 4.6 --- xiaohongshu/publish.go | 109 +++++++++++++++++++++-------------- xiaohongshu/publish_video.go | 21 +++++-- 2 files changed, 82 insertions(+), 48 deletions(-) diff --git a/xiaohongshu/publish.go b/xiaohongshu/publish.go index 29bd950..6d64df3 100644 --- a/xiaohongshu/publish.go +++ b/xiaohongshu/publish.go @@ -255,12 +255,16 @@ func waitForUploadComplete(page *rod.Page, expectedCount int) error { } func submitPublish(page *rod.Page, title, content string, tags []string, scheduleTime *time.Time) error { + titleElem, err := page.Element("div.d-input input") + if err != nil { + return errors.Wrap(err, "查找标题输入框失败") + } + if err := titleElem.Input(title); err != nil { + return errors.Wrap(err, "输入标题失败") + } - titleElem := page.MustElement("div.d-input input") - titleElem.MustInput(title) - - // 检查一下 title 的长度 - time.Sleep(500 * time.Millisecond) // 等待页面渲染长度提示 + // 检查标题长度 + time.Sleep(500 * time.Millisecond) if err := checkTitleMaxLength(page); err != nil { return err } @@ -268,18 +272,20 @@ func submitPublish(page *rod.Page, title, content string, tags []string, schedul time.Sleep(1 * time.Second) - if contentElem, ok := getContentElement(page); ok { - contentElem.MustInput(content) - - inputTags(contentElem, tags) - - } else { + contentElem, ok := getContentElement(page) + if !ok { return errors.New("没有找到内容输入框") } + if err := contentElem.Input(content); err != nil { + return errors.Wrap(err, "输入正文失败") + } + if err := inputTags(contentElem, tags); err != nil { + return err + } time.Sleep(1 * time.Second) - // 正文的长度的判定: + // 检查正文长度 if err := checkContentMaxLength(page); err != nil { return err } @@ -293,11 +299,15 @@ func submitPublish(page *rod.Page, title, content string, tags []string, schedul slog.Info("定时发布设置完成", "schedule_time", scheduleTime.Format("2006-01-02 15:04")) } - submitButton := page.MustElement(".publish-page-publish-btn button.bg-red") - submitButton.MustClick() + submitButton, err := page.Element(".publish-page-publish-btn button.bg-red") + if err != nil { + return errors.Wrap(err, "查找发布按钮失败") + } + if err := submitButton.Click(proto.InputMouseButtonLeft, 1); err != nil { + return errors.Wrap(err, "点击发布按钮失败") + } time.Sleep(3 * time.Second) - return nil } @@ -379,39 +389,53 @@ func getContentElement(page *rod.Page) (*rod.Element, bool) { return nil, false } -func inputTags(contentElem *rod.Element, tags []string) { +func inputTags(contentElem *rod.Element, tags []string) error { if len(tags) == 0 { - return + return nil } time.Sleep(1 * time.Second) for i := 0; i < 20; i++ { - contentElem.MustKeyActions(). - Type(input.ArrowDown). - MustDo() + ka, err := contentElem.KeyActions() + if err != nil { + return errors.Wrap(err, "创建键盘操作失败") + } + if err := ka.Type(input.ArrowDown).Do(); err != nil { + return errors.Wrap(err, "按下方向键失败") + } time.Sleep(10 * time.Millisecond) } - contentElem.MustKeyActions(). - Press(input.Enter). - Press(input.Enter). - MustDo() + ka, err := contentElem.KeyActions() + if err != nil { + return errors.Wrap(err, "创建键盘操作失败") + } + if err := ka.Press(input.Enter).Press(input.Enter).Do(); err != nil { + return errors.Wrap(err, "按下回车键失败") + } time.Sleep(1 * time.Second) for _, tag := range tags { tag = strings.TrimLeft(tag, "#") - inputTag(contentElem, tag) + if err := inputTag(contentElem, tag); err != nil { + return errors.Wrapf(err, "输入标签[%s]失败", tag) + } } + return nil } -func inputTag(contentElem *rod.Element, tag string) { - contentElem.MustInput("#") +func inputTag(contentElem *rod.Element, tag string) error { + if err := contentElem.Input("#"); err != nil { + return errors.Wrap(err, "输入#失败") + } time.Sleep(200 * time.Millisecond) for _, char := range tag { - contentElem.MustInput(string(char)) + if err := contentElem.Input(string(char)); err != nil { + return errors.Wrapf(err, "输入字符[%c]失败", char) + } time.Sleep(50 * time.Millisecond) } @@ -419,24 +443,25 @@ func inputTag(contentElem *rod.Element, tag string) { page := contentElem.Page() topicContainer, err := page.Element("#creator-editor-topic-container") - if err == nil && topicContainer != nil { - firstItem, err := topicContainer.Element(".item") - if err == nil && firstItem != nil { - firstItem.MustClick() - slog.Info("成功点击标签联想选项", "tag", tag) - time.Sleep(200 * time.Millisecond) - } else { - slog.Warn("未找到标签联想选项,直接输入空格", "tag", tag) - // 如果没有找到联想选项,输入空格结束 - contentElem.MustInput(" ") - } - } else { + if err != nil || topicContainer == nil { slog.Warn("未找到标签联想下拉框,直接输入空格", "tag", tag) - // 如果没有找到下拉框,输入空格结束 - contentElem.MustInput(" ") + return contentElem.Input(" ") } + firstItem, err := topicContainer.Element(".item") + if err != nil || firstItem == nil { + slog.Warn("未找到标签联想选项,直接输入空格", "tag", tag) + return contentElem.Input(" ") + } + + if err := firstItem.Click(proto.InputMouseButtonLeft, 1); err != nil { + return errors.Wrap(err, "点击标签联想选项失败") + } + slog.Info("成功点击标签联想选项", "tag", tag) + time.Sleep(200 * time.Millisecond) + time.Sleep(500 * time.Millisecond) // 等待标签处理完成 + return nil } func findTextboxByPlaceholder(page *rod.Page) (*rod.Element, error) { diff --git a/xiaohongshu/publish_video.go b/xiaohongshu/publish_video.go index 614d561..5ea7263 100644 --- a/xiaohongshu/publish_video.go +++ b/xiaohongshu/publish_video.go @@ -132,17 +132,26 @@ func waitForPublishButtonClickable(page *rod.Page) (*rod.Element, error) { // submitPublishVideo 填写标题、正文、标签并点击发布(等待按钮可点击后再提交) func submitPublishVideo(page *rod.Page, title, content string, tags []string, scheduleTime *time.Time) error { // 标题 - titleElem := page.MustElement("div.d-input input") - titleElem.MustInput(title) + titleElem, err := page.Element("div.d-input input") + if err != nil { + return errors.Wrap(err, "查找标题输入框失败") + } + if err := titleElem.Input(title); err != nil { + return errors.Wrap(err, "输入标题失败") + } time.Sleep(1 * time.Second) // 正文 + 标签 - if contentElem, ok := getContentElement(page); ok { - contentElem.MustInput(content) - inputTags(contentElem, tags) - } else { + contentElem, ok := getContentElement(page) + if !ok { return errors.New("没有找到内容输入框") } + if err := contentElem.Input(content); err != nil { + return errors.Wrap(err, "输入正文失败") + } + if err := inputTags(contentElem, tags); err != nil { + return err + } time.Sleep(1 * time.Second)