geoGo/internal/publisher/zh.go

386 lines
9.3 KiB
Go
Raw Permalink 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 publisher
import (
"context"
"fmt"
"geo/internal/config"
"log"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
)
type ZhihuPublisher struct {
*BasePublisher
}
func NewZhihuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface {
return &ZhihuPublisher{NewBasePublisher(ctx, task, cfg, logger)}
}
func (p *ZhihuPublisher) CheckLoginStatus() bool {
currentURL := p.GetCurrentURL()
if strings.Contains(currentURL, p.LoginURL) {
return false
}
return true
}
func (p *ZhihuPublisher) WaitLogin() (bool, string) {
p.LogInfo("开始等待登录...")
if err := p.SetupDriver(); err != nil {
return false, fmt.Sprintf("浏览器启动失败: %v", err)
}
defer p.Close()
if p.EditorURL != "" {
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 {
currentURL := p.GetCurrentURL()
if p.EditorURL != "" && strings.Contains(currentURL, p.EditorURL) {
p.SaveCookies()
return true, "login_success"
}
if p.LoginedURL != "" && strings.Contains(currentURL, p.LoginedURL) {
p.SaveCookies()
return true, "login_success"
}
if p.CheckLoginStatus() {
p.SaveCookies()
return true, "login_success"
}
p.SleepMs(1000)
}
return false, "登录超时,请检查网络或账号状态"
}
func (p *ZhihuPublisher) waitForEditorReady(timeout int) bool {
p.LogInfo("等待编辑器加载...")
startTime := time.Now()
for time.Since(startTime) < time.Duration(timeout)*time.Second {
titleSelectors := []string{
".WriteIndex-titleInput textarea",
".DraftEditor-root",
".public-DraftEditor-content",
"[contenteditable='true']",
}
for _, selector := range titleSelectors {
el, err := p.Page.Element(selector)
if err == nil && el != nil {
visible, _ := el.Visible()
if visible {
p.LogInfo("编辑器加载完成")
return true
}
}
}
p.SleepMs(1000)
}
p.LogInfo("编辑器加载超时")
return false
}
func (p *ZhihuPublisher) inputTitle() error {
p.LogInfo("输入文章标题...")
titleSelectors := []string{
".WriteIndex-titleInput textarea",
"textarea[placeholder*='标题']",
".Input[placeholder*='标题']",
".title-input textarea",
}
for _, selector := range titleSelectors {
titleInput, err := p.WaitForElementVisible(selector, 5)
if err == nil && titleInput != nil {
p.LogInfo(fmt.Sprintf("找到标题输入框: %s", selector))
p.ClearInput(titleInput)
p.SleepMs(300)
titleInput.Input("")
p.SleepMs(300)
titleInput.Input(p.Title)
p.LogInfo(fmt.Sprintf("标题已输入: %s", p.Title))
titleInput.Evaluate(&rod.EvalOptions{
JS: `(el) => {
el.dispatchEvent(new Event('input', {bubbles: true}));
el.dispatchEvent(new Event('change', {bubbles: true}));
el.dispatchEvent(new Event('blur', {bubbles: true}));
}`,
})
p.SleepMs(500)
return nil
}
}
return fmt.Errorf("未找到标题输入框")
}
func (p *ZhihuPublisher) importDocument() error {
if p.SourcePath == "" {
p.LogInfo("未提供文档路径或文档不存在")
return fmt.Errorf("文档不存在")
}
p.LogInfo(fmt.Sprintf("尝试导入文档: %s", p.SourcePath))
// 步骤1: 查找并点击"导入"按钮
p.LogInfo("步骤1: 查找并点击'导入'按钮...")
importBtn, err := p.Page.Element("button[aria-label='导入']")
if err != nil {
buttons, _ := p.Page.Elements("button")
for _, btn := range buttons {
text, _ := btn.Text()
if text == "导入" {
importBtn = btn
p.LogInfo("通过文本找到导入按钮")
break
}
}
}
if importBtn == nil {
return fmt.Errorf("未找到导入按钮")
}
importBtn.Click(proto.InputMouseButtonLeft, 1)
p.LogInfo("已点击导入按钮")
p.SleepMs(1000)
// 步骤2: 查找并点击"导入文档"按钮
p.LogInfo("步骤2: 查找并点击'导入文档'按钮...")
docImportBtn, err := p.Page.Element("button[aria-label='导入文档']")
if err != nil {
menuButtons, _ := p.Page.Elements(".Menu button, .Popover-content button")
for _, btn := range menuButtons {
text, _ := btn.Text()
if strings.Contains(text, "导入文档") {
docImportBtn = btn
p.LogInfo("通过文本找到导入文档按钮")
break
}
}
}
if docImportBtn == nil {
return fmt.Errorf("未找到导入文档按钮")
}
docImportBtn.Click(proto.InputMouseButtonLeft, 1)
p.LogInfo("已点击导入文档按钮")
p.SleepMs(1000)
// 步骤3: 查找file input并上传文档
p.LogInfo("步骤3: 查找文件上传输入框...")
var fileInput *rod.Element
for i := 0; i < 10; i++ {
fileInput, _ = p.Page.Element("input[type='file'][accept='.docx,.markdown,.mdown,.mkdn,.md']")
if fileInput != nil {
p.LogInfo("找到导入文档输入框")
break
}
if fileInput == nil {
hiddenInputs, _ := p.Page.Elements("input[type='file'][style*='display: none']")
for _, inp := range hiddenInputs {
accept, _ := inp.Attribute("accept")
if accept != nil && (strings.Contains(*accept, ".docx") || strings.Contains(*accept, ".md")) {
fileInput = inp
p.LogInfo("找到隐藏的导入文档输入框")
break
}
}
}
if fileInput != nil {
break
}
p.SleepMs(500)
}
if fileInput == nil {
return fmt.Errorf("未找到文件上传输入框")
}
p.LogInfo(fmt.Sprintf("开始上传文档: %s", p.SourcePath))
fileInput.SetFiles([]string{p.SourcePath})
p.LogInfo(fmt.Sprintf("文档已上传: %s", p.SourcePath))
p.Sleep(3)
// 等待导入完成
success := false
for i := 0; i < 60; i++ {
p.SleepMs(1000)
toasts, _ := p.Page.Elements(".Toast-module_toast, .el-message--success, .toast-success")
for _, toast := range toasts {
visible, _ := toast.Visible()
if visible {
text, _ := toast.Text()
if strings.Contains(text, "成功") || strings.Contains(text, "导入") || strings.Contains(text, "完成") {
p.LogInfo(fmt.Sprintf("导入成功提示: %s", text))
success = true
break
}
}
}
if success {
break
}
editors, _ := p.Page.Elements("[contenteditable='true'], .DraftEditor-root, .ProseMirror")
for _, editor := range editors {
text, _ := editor.Text()
if len(text) > 10 {
p.LogInfo(fmt.Sprintf("文档导入成功,内容长度: %d", len(text)))
success = true
break
}
}
if success {
break
}
}
if success {
p.SleepMs(2000)
p.LogInfo("文档导入完成")
return nil
}
p.LogInfo("文档导入状态未知")
return nil
}
func (p *ZhihuPublisher) clickPublish() error {
p.LogInfo("点击发布按钮...")
publishSelectors := []string{
".Button--primary:contains('发布')",
".css-d0uhtl",
"button:contains('发布')",
".PublishButton",
"[class*='publish'] button.Button--primary",
}
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 {
buttons, _ := p.Page.Elements("button")
for _, btn := range buttons {
visible, _ := btn.Visible()
if visible {
text, _ := btn.Text()
if text == "发布" || strings.Contains(text, "发布") {
publishBtn = btn
p.LogInfo("通过遍历按钮找到发布按钮")
break
}
}
}
}
if publishBtn == nil {
return fmt.Errorf("未找到发布按钮")
}
p.SleepMs(500)
if err := publishBtn.Click(proto.InputMouseButtonLeft, 1); err != nil {
if err := p.JSClick(publishBtn); err != nil {
return fmt.Errorf("点击发布按钮失败: %v", err)
}
}
p.LogInfo("已点击发布按钮")
return nil
}
func (p *ZhihuPublisher) waitForPublishResult(timeout int) (bool, string) {
p.LogInfo("等待发布结果...")
p.SleepMs(500)
startTime := time.Now()
for time.Since(startTime) < time.Duration(timeout)*time.Second {
currentURL := p.GetCurrentURL()
// 检查失败弹窗
exist, failedDiv, _ := p.Page.Has(".Notification-textSection")
if exist {
failedReason, _ := failedDiv.Text()
p.LogInfo(fmt.Sprintf("发布失败: %s", failedReason))
return false, failedReason
}
if !strings.Contains(currentURL, "/edit") {
p.LogInfo(fmt.Sprintf("发布成功URL: %s", currentURL))
return true, "发布成功"
}
p.SleepMs(1000)
}
return false, "发布结果未知(超时)"
}
func (p *ZhihuPublisher) 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.inputTitle},
{"导入内容", p.importDocument},
{"点击发布", 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
}