203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
package publisher
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"geo/internal/config"
|
||
"log"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/go-rod/rod"
|
||
)
|
||
|
||
type CSDNPublisher struct {
|
||
*BasePublisher
|
||
}
|
||
|
||
func NewCSDNPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface {
|
||
return &CSDNPublisher{NewBasePublisher(ctx, task, cfg, logger)}
|
||
}
|
||
|
||
func (p *CSDNPublisher) 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()
|
||
p.LogInfo("已有登录状态")
|
||
return true, "already_logged_in"
|
||
}
|
||
|
||
startTime := time.Now()
|
||
timeout := 120
|
||
for time.Since(startTime) < time.Duration(timeout)*time.Second {
|
||
time.Sleep(1 * time.Second)
|
||
if p.CheckLoginStatus() {
|
||
p.SaveCookies()
|
||
p.LogInfo("登录成功")
|
||
return true, "login_success"
|
||
}
|
||
p.SleepMs(1000)
|
||
}
|
||
|
||
return false, "登录超时,请检查网络或账号状态"
|
||
}
|
||
|
||
func (p *CSDNPublisher) inputTitle() error {
|
||
p.LogInfo("输入文章标题...")
|
||
|
||
titleInput, err := p.WaitForElementVisible("#txtTitle", 5)
|
||
if err != nil {
|
||
return fmt.Errorf("未找到标题输入框: %v", err)
|
||
}
|
||
|
||
p.LogInfo("找到标题输入框")
|
||
|
||
if err := p.ClearInput(titleInput); err != nil {
|
||
titleInput.Input("")
|
||
}
|
||
p.SleepMs(200)
|
||
|
||
if err := p.SetInputValue(titleInput, p.Title); err != nil {
|
||
titleInput.Input(p.Title)
|
||
}
|
||
|
||
p.LogInfo(fmt.Sprintf("标题已输入: %s", p.Title))
|
||
|
||
p.SleepMs(300)
|
||
return nil
|
||
}
|
||
|
||
func (p *CSDNPublisher) inputContent(titleInput *rod.Element) error {
|
||
p.LogInfo("输入文章内容...")
|
||
|
||
p.SleepMs(1000)
|
||
return nil
|
||
}
|
||
|
||
func (p *CSDNPublisher) clickPublish() error {
|
||
p.LogInfo("点击发布按钮...")
|
||
|
||
btnBox, err := p.WaitForElementVisible("div.btn-box", 3)
|
||
if err != nil {
|
||
return fmt.Errorf("未找到发布按钮区域: %v", err)
|
||
}
|
||
|
||
publishBtn, err := btnBox.ElementX(".//button[contains(@class, 'btn-outline-danger') and .//span[text()='发布博客']]")
|
||
if err != nil {
|
||
publishBtn, err = p.Page.ElementX("//button[contains(@class, 'btn-outline-danger') and .//span[text()='发布博客']]")
|
||
if err != nil {
|
||
return fmt.Errorf("未找到发布按钮: %v", err)
|
||
}
|
||
}
|
||
|
||
if err := publishBtn.ScrollIntoView(); err != nil {
|
||
p.LogInfo(fmt.Sprintf("滚动到发布按钮失败: %v", err))
|
||
}
|
||
p.SleepMs(300)
|
||
|
||
if err := p.JSClick(publishBtn); err != nil {
|
||
return fmt.Errorf("点击发布按钮失败: %v", err)
|
||
}
|
||
|
||
p.LogInfo("已点击发布按钮")
|
||
p.SleepMs(2000)
|
||
return nil
|
||
}
|
||
|
||
func (p *CSDNPublisher) waitForPublishResult() (bool, string) {
|
||
p.LogInfo("等待发布结果...")
|
||
|
||
for attempt := 0; attempt < 10; attempt++ {
|
||
currentURL := p.GetCurrentURL()
|
||
p.LogInfo(fmt.Sprintf("第 %d 次检查 - URL: %s", attempt+1, currentURL))
|
||
|
||
if strings.Contains(currentURL, "success") {
|
||
p.LogInfo(fmt.Sprintf("发布成功!URL: %s", currentURL))
|
||
return true, "发布成功"
|
||
}
|
||
|
||
errorSelectors := []string{
|
||
"[class*='el_mcm-message--error'] .el_mcm-message__content",
|
||
"[class*='el_mcm-message--error']",
|
||
"[class*='message--error']",
|
||
".error",
|
||
".alert-error",
|
||
}
|
||
|
||
for _, selector := range errorSelectors {
|
||
errorElements, _ := p.Page.Elements(selector)
|
||
for _, elem := range errorElements {
|
||
visible, _ := elem.Visible()
|
||
if visible {
|
||
text, _ := elem.Text()
|
||
if text != "" {
|
||
p.LogError(fmt.Sprintf("发布失败: %s", text))
|
||
return false, fmt.Sprintf("发布失败: %s", text)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 截图保存用于调试
|
||
if attempt == 1 || attempt == 9 {
|
||
screenshotPath := fmt.Sprintf("debug_%s_%d.png", p.RequestID, attempt)
|
||
p.Screenshot(screenshotPath)
|
||
p.LogInfo(fmt.Sprintf("已保存截图: %s", screenshotPath))
|
||
}
|
||
|
||
p.SleepMs(1000)
|
||
}
|
||
|
||
return false, "发布结果未知"
|
||
}
|
||
|
||
func (p *CSDNPublisher) PublishNote() (bool, string) {
|
||
p.StartNote()
|
||
|
||
// 初始化浏览器
|
||
if err := p.SetupDriver(); err != nil {
|
||
return false, fmt.Sprintf("浏览器启动失败: %v", err)
|
||
}
|
||
defer p.Close()
|
||
|
||
// 执行发布流程
|
||
steps := []struct {
|
||
name string
|
||
fn func() error
|
||
}{
|
||
{"初始化", p.InitPage},
|
||
{"保存cookie", p.SaveCookies},
|
||
{"输入标题", p.inputTitle},
|
||
{"输入内容", func() error {
|
||
titleInput, _ := p.WaitForElementVisible("#txtTitle", 5)
|
||
return p.inputContent(titleInput)
|
||
}},
|
||
{"点击发布", 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, "")
|
||
p.SleepMs(500)
|
||
}
|
||
|
||
// 等待发布结果
|
||
return p.waitForPublishResult()
|
||
}
|
||
|
||
func (p *CSDNPublisher) LogWarning(message string) {
|
||
p.Logger.Printf("⚠️ %s", message)
|
||
}
|