337 lines
7.6 KiB
Go
337 lines
7.6 KiB
Go
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(10)
|
||
|
||
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.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
|
||
}
|