package publisher import ( "context" "fmt" "geo/internal/config" "log" "strings" "time" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" ) type JianshuPublisher struct { *BasePublisher } func NewJianshuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { return &JianshuPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *JianshuPublisher) CheckLogin() (bool, string) { p.LogInfo("检查登录状态...") if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } defer p.Close() p.Page.MustNavigate(p.EditorURL) p.Sleep(3) p.WaitForPageReady(5) if p.CheckLoginStatus() { p.SaveCookies() return true, "已登录" } return false, "未登录" } func (p *JianshuPublisher) CheckLoginStatus() bool { currentURL := p.GetCurrentURL() if strings.Contains(currentURL, p.LoginURL) { return false } return true } func (p *JianshuPublisher) WaitLogin() (bool, string) { p.LogInfo("开始等待登录...") if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } defer p.Close() p.Page.MustNavigate(p.EditorURL) p.Sleep(3) if p.CheckLoginStatus() { p.SaveCookies() return true, "already_logged_in" } startTime := time.Now() timeout := 240 for time.Since(startTime) < time.Duration(timeout)*time.Second { if p.CheckLoginStatus() { p.SaveCookies() return true, "login_success" } p.SleepMs(1000) } return false, "登录超时,请检查网络或账号状态" } func (p *JianshuPublisher) waitForEditorReady() error { p.LogInfo("等待编辑器加载...") plusIcon, err := p.WaitForElementVisible(".fa-plus-circle", 10) if err == nil && plusIcon != nil { p.JSClick(plusIcon) p.LogInfo("已点击新建文章按钮") p.Sleep(2) } else { return fmt.Errorf("未找到新建文章按钮") } startTime := time.Now() for time.Since(startTime) < time.Duration(60)*time.Second { editor, _ := p.WaitForElementVisible("#arthur-editor", 2) if editor != nil { p.LogInfo("编辑器加载完成") return nil } p.SleepMs(1000) } p.LogInfo("编辑器加载超时") return nil } func (p *JianshuPublisher) inputTitle() error { p.LogInfo("输入文章标题...") today := time.Now().Format("2006-01-02") selector := fmt.Sprintf("input[value='%s']", today) titleInput, err := p.WaitForElementVisible(selector, 5) if err != nil { return fmt.Errorf("未找到标题输入框") } p.LogInfo("找到标题输入框") p.ClearInput(titleInput) p.SleepMs(300) titleInput.Input("") p.SleepMs(300) titleInput.Input(p.Title) return nil } func (p *JianshuPublisher) inputContent() error { p.LogInfo("输入文章正文...") if p.Content == "" { p.LogInfo("内容为空") return fmt.Errorf("内容为空") } textArea, err := p.WaitForElementVisible("#arthur-editor", 10) if err != nil { return fmt.Errorf("未找到文本输入框: %v", err) } p.LogInfo("找到文本输入框") p.JSClick(textArea) p.SleepMs(500) textArea.Input(p.Content) p.SleepMs(2000) p.LogInfo(fmt.Sprintf("内容已输入,长度: %d", len(p.Content))) return nil } func (p *JianshuPublisher) clickPublish() error { p.LogInfo("点击发布按钮...") publishSelectors := []string{ "a[data-action='publicize']", ".publish-btn", ".submit-btn", "button:contains('发布')", ".btn-publish", "[class*='publish'] button", "a:contains('发布文章')", } var publishBtn *rod.Element for _, selector := range publishSelectors { publishBtn, _ = p.WaitForElementClickable(selector, 5) if publishBtn != nil { visible, _ := publishBtn.Visible() if visible { p.LogInfo(fmt.Sprintf("找到发布按钮: %s", selector)) break } } } if publishBtn == nil { links, _ := p.Page.Elements("a") for _, link := range links { text, _ := link.Text() if strings.Contains(text, "发布文章") || strings.Contains(text, "发布") { publishBtn = link p.LogInfo("通过遍历链接找到发布按钮") break } } } if publishBtn == nil { return fmt.Errorf("未找到发布按钮") } if err := publishBtn.Click(proto.InputMouseButtonLeft, 1); err != nil { if err := p.JSClick(publishBtn); err != nil { return fmt.Errorf("点击发布按钮失败: %v", err) } } p.LogInfo("已点击发布按钮") p.Sleep(3) return nil } func (p *JianshuPublisher) handlePublishDialog() error { p.LogInfo("处理发布弹窗...") dialogSelectors := []string{ ".ant-modal", ".el-dialog", ".publish-dialog", "[class*='dialog']", "[role='dialog']", } var dialog *rod.Element for _, selector := range dialogSelectors { dialog, _ = p.WaitForElementVisible(selector, 5) if dialog != nil { p.LogInfo(fmt.Sprintf("找到发布弹窗: %s", selector)) break } } if dialog == nil { p.LogInfo("未发现发布弹窗") return nil } confirmSelectors := []string{ "button:contains('确认发布')", "button:contains('确定')", "button:contains('发布')", ".confirm-btn", ".ok-btn", } for _, selector := range confirmSelectors { confirmBtn, err := p.WaitForElementClickable(selector, 3) if err == nil && confirmBtn != nil { p.JSClick(confirmBtn) p.LogInfo("已确认发布") p.Sleep(2) return nil } } closeSelectors := []string{ ".ant-modal-close", ".el-dialog__close", ".close-btn", } for _, selector := range closeSelectors { closeBtn, err := p.WaitForElementClickable(selector, 2) if err == nil && closeBtn != nil { p.JSClick(closeBtn) p.LogInfo("关闭发布弹窗") p.SleepMs(1000) break } } return nil } func (p *JianshuPublisher) waitForPublishResult(timeout int) (bool, string) { p.LogInfo("等待发布结果...") startTime := time.Now() for time.Since(startTime) < time.Duration(timeout)*time.Second { currentURL := p.GetCurrentURL() successKeywords := []string{"notes", "articles", "success", "published"} for _, keyword := range successKeywords { if strings.Contains(strings.ToLower(currentURL), keyword) { p.LogInfo(fmt.Sprintf("发布成功!URL: %s", currentURL)) return true, "发布成功" } } successSelectors := []string{ ".ant-message-success", ".el-message--success", ".toast-success", "[class*='success']", } for _, selector := range successSelectors { elems, _ := p.Page.Elements(selector) for _, elem := range elems { visible, _ := elem.Visible() if visible { text, _ := elem.Text() if strings.Contains(text, "成功") || strings.Contains(strings.ToLower(text), "success") || strings.Contains(text, "已发布") { p.LogInfo(fmt.Sprintf("发布成功: %s", text)) return true, text } } } } p.SleepMs(1000) } return false, "发布结果未知(超时)" } func (p *JianshuPublisher) PublishNote() (bool, string) { p.StartNote() if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } defer p.Page.Close() steps := []struct { name string fn func() error }{ {"初始化页面", p.InitPage}, {"创建编辑器", p.waitForEditorReady}, {"输入标题", p.inputTitle}, {"导入内容", p.inputContent}, {"点击发布", p.clickPublish}, } for _, step := range steps { if err := step.fn(); err != nil { p.LogStep(step.name, false, err.Error()) return false, fmt.Sprintf("%s失败: %v", step.name, err) } p.LogStep(step.name, true, "") } success, message := p.waitForPublishResult(60) if success { p.LogInfo(fmt.Sprintf("🎉 发布成功!%s", message)) return true, message } p.LogError(fmt.Sprintf("发布失败: %s", message)) return false, message }