geoGo/internal/publisher/baijiahao.go

384 lines
9.6 KiB
Go
Raw 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 (
"fmt"
"strings"
"time"
"geo/internal/config"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
)
type BaijiahaoPublisher struct {
*BasePublisher
Category string
ArticleType string
IsTop bool
}
func NewBaijiahaoPublisher(headless bool, title, content string, tags []string, tenantID, platIndex, requestID, imagePath, wordPath string, platInfo map[string]interface{}, cfg *config.Config) *BaijiahaoPublisher {
base := NewBasePublisher(headless, title, content, tags, tenantID, platIndex, requestID, imagePath, wordPath, platInfo, cfg)
if platInfo != nil {
base.LoginURL = getString(platInfo, "login_url")
base.EditorURL = getString(platInfo, "edit_url")
base.LoginedURL = getString(platInfo, "logined_url")
}
return &BaijiahaoPublisher{BasePublisher: base}
}
func (p *BaijiahaoPublisher) CheckLoginStatus() bool {
url := p.GetCurrentURL()
// 如果URL包含登录相关关键词表示未登录
if strings.Contains(url, "login") || strings.Contains(url, "passport") {
return false
}
// 如果URL是编辑页面或主页表示已登录
if strings.Contains(url, "baijiahao") || strings.Contains(url, "edit") {
return true
}
return url != p.LoginURL
}
func (p *BaijiahaoPublisher) CheckLogin() (bool, string) {
p.LogInfo("检查登录状态...")
if err := p.SetupDriver(); err != nil {
return false, fmt.Sprintf("浏览器启动失败: %v", err)
}
defer p.Close()
p.Page.MustNavigate(p.LoginedURL)
p.Sleep(3)
p.WaitForPageReady(5)
if p.CheckLoginStatus() {
p.SaveCookies()
return true, "已登录"
}
return false, "未登录"
}
func (p *BaijiahaoPublisher) WaitLogin() (bool, string) {
p.LogInfo("开始等待登录...")
if err := p.SetupDriver(); err != nil {
return false, fmt.Sprintf("浏览器启动失败: %v", err)
}
defer p.Close()
// 先尝试访问已登录页面
p.Page.MustNavigate(p.LoginedURL)
p.Sleep(3)
if p.CheckLoginStatus() {
p.SaveCookies()
p.LogInfo("已有登录状态")
return true, "already_logged_in"
}
// 未登录,跳转到登录页
p.Page.MustNavigate(p.LoginURL)
p.LogInfo("请扫描二维码登录...")
// 等待登录完成最多120秒
for i := 0; i < 120; i++ {
time.Sleep(1 * time.Second)
if p.CheckLoginStatus() {
p.SaveCookies()
p.LogInfo("登录成功")
return true, "login_success"
}
}
return false, "登录超时"
}
func (p *BaijiahaoPublisher) inputTitle() error {
p.LogInfo("输入标题...")
titleSelectors := []string{
".client_pages_edit_components_titleInput [contenteditable='true']",
".input-box [contenteditable='true']",
"[contenteditable='true']",
}
var titleInput *rod.Element
var err error
for _, selector := range titleSelectors {
titleInput, err = p.WaitForElementVisible(selector, 5)
if err == nil && titleInput != nil {
p.LogInfo(fmt.Sprintf("找到标题输入框: %s", selector))
break
}
}
if titleInput == nil {
return fmt.Errorf("未找到标题输入框")
}
// 点击获取焦点
if err := titleInput.Click(proto.InputMouseButtonLeft, 1); err != nil {
return fmt.Errorf("点击标题框失败: %v", err)
}
p.SleepMs(500)
// 清空输入框
if err := p.ClearContentEditable(titleInput); err != nil {
p.LogInfo(fmt.Sprintf("清空标题框失败: %v", err))
}
p.SleepMs(300)
// 输入标题
if err := p.SetContentEditable(titleInput, p.Title); err != nil {
// 备用输入方式
titleInput.Input(p.Title)
}
p.LogInfo(fmt.Sprintf("标题已输入: %s", p.Title))
return nil
}
func (p *BaijiahaoPublisher) inputContent() error {
p.LogInfo("输入内容...")
// 查找内容编辑器
contentEditor, err := p.WaitForElementVisible(".ProseMirror", 10)
if err != nil {
contentEditor, err = p.WaitForElementVisible("[contenteditable='true']", 10)
if err != nil {
return fmt.Errorf("未找到内容编辑器: %v", err)
}
}
// 点击获取焦点
if err := contentEditor.Click(proto.InputMouseButtonLeft, 1); err != nil {
return fmt.Errorf("点击编辑器失败: %v", err)
}
p.SleepMs(500)
// 清空编辑器
if err := p.ClearContentEditable(contentEditor); err != nil {
p.LogInfo(fmt.Sprintf("清空编辑器失败: %v", err))
}
p.SleepMs(300)
// 输入内容
if err := p.SetContentEditable(contentEditor, p.Content); err != nil {
// 备用输入方式
contentEditor.Input(p.Content)
}
p.LogInfo(fmt.Sprintf("内容已输入,长度: %d", len(p.Content)))
return nil
}
func (p *BaijiahaoPublisher) uploadImage() error {
if p.ImagePath == "" {
p.LogInfo("无封面图片,跳过")
return nil
}
p.LogInfo(fmt.Sprintf("上传封面: %s", p.ImagePath))
// 查找封面区域
coverArea, err := p.WaitForElementClickable(".cheetah-spin-container", 5)
if err != nil {
p.LogInfo("未找到封面区域,跳过")
return nil
}
if err := coverArea.Click(proto.InputMouseButtonLeft, 1); err != nil {
p.LogInfo(fmt.Sprintf("点击封面区域失败: %v", err))
}
p.SleepMs(1000)
// 查找文件输入框
fileInput, err := p.Page.Element("input[type='file'][accept*='image']")
if err != nil {
fileInput, err = p.Page.Element("input[type='file']")
if err != nil {
return fmt.Errorf("未找到文件输入框: %v", err)
}
}
// 上传图片
if err := fileInput.SetFiles([]string{p.ImagePath}); err != nil {
return fmt.Errorf("上传图片失败: %v", err)
}
p.LogInfo("图片上传成功")
p.Sleep(3)
// 查找确认按钮
confirmBtn, err := p.WaitForElementClickable(".cheetah-btn-primary", 5)
if err == nil && confirmBtn != nil {
if err := confirmBtn.Click(proto.InputMouseButtonLeft, 1); err != nil {
p.LogInfo(fmt.Sprintf("点击确认按钮失败: %v", err))
}
p.LogInfo("已确认封面")
p.SleepMs(1000)
}
return nil
}
func (p *BaijiahaoPublisher) clickPublish() error {
p.LogInfo("点击发布按钮...")
// 滚动到底部
if _, err := p.Page.Eval(`() => window.scrollTo(0, document.body.scrollHeight)`); err != nil {
p.LogInfo(fmt.Sprintf("滚动到底部失败: %v", err))
}
p.SleepMs(1000)
// 查找发布按钮
publishSelectors := []string{
"[data-testid='publish-btn']",
".op-list-right .cheetah-btn-primary",
".cheetah-btn-primary",
"button:contains('发布')",
}
var publishBtn *rod.Element
var err error
for _, selector := range publishSelectors {
publishBtn, err = p.WaitForElementClickable(selector, 5)
if err == nil && publishBtn != nil {
p.LogInfo(fmt.Sprintf("找到发布按钮: %s", selector))
break
}
}
// 如果还是没找到,通过 XPath 查找
if publishBtn == nil {
publishBtn, err = p.Page.ElementX("//button[contains(text(), '发布')]")
if err != nil {
return fmt.Errorf("未找到发布按钮: %v", err)
}
}
// 滚动到按钮位置
if err := p.ScrollToElement(publishBtn); err != nil {
p.LogInfo(fmt.Sprintf("滚动到按钮失败: %v", err))
}
p.SleepMs(500)
// 点击发布
if err := publishBtn.Click(proto.InputMouseButtonLeft, 1); err != nil {
return fmt.Errorf("点击发布按钮失败: %v", err)
}
p.LogInfo("已点击发布按钮")
return nil
}
func (p *BaijiahaoPublisher) waitForPublishResult() (bool, string) {
p.LogInfo("等待发布结果...")
// 等待最多60秒
for i := 0; i < 60; i++ {
p.SleepMs(1000)
// 检查URL是否跳转到成功页面
currentURL := p.GetCurrentURL()
if strings.Contains(currentURL, "clue") ||
strings.Contains(currentURL, "success") ||
strings.Contains(currentURL, "article/list") {
p.LogInfo("发布成功!")
return true, "发布成功"
}
// 检查是否有成功提示
elements, _ := p.Page.Elements(".cheetah-message-success, .cheetah-message-info, [class*='success']")
for _, el := range elements {
text, _ := el.Text()
if strings.Contains(text, "成功") || strings.Contains(text, "已发布") {
p.LogInfo(fmt.Sprintf("发布成功: %s", text))
return true, text
}
}
// 检查是否有失败提示
elements, _ = p.Page.Elements(".cheetah-message-error, .cheetah-message-warning, [class*='error']")
for _, el := range elements {
text, _ := el.Text()
if strings.Contains(text, "失败") || strings.Contains(text, "错误") {
p.LogError(fmt.Sprintf("发布失败: %s", text))
return false, text
}
}
}
return false, "发布结果未知(超时)"
}
func (p *BaijiahaoPublisher) PublishNote() (bool, string) {
p.LogInfo(strings.Repeat("=", 50))
p.LogInfo("开始发布百家号文章...")
p.LogInfo(fmt.Sprintf("标题: %s", p.Title))
p.LogInfo(fmt.Sprintf("内容长度: %d", len(p.Content)))
p.LogInfo(strings.Repeat("=", 50))
// 初始化浏览器
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)
// 尝试加载cookies
if err := p.LoadCookies(); err == nil {
p.RefreshPage()
p.Sleep(2)
if p.CheckLoginStatus() {
p.LogInfo("使用cookies登录成功")
} else {
p.LogInfo("cookies已过期需要重新登录")
return false, "需要登录"
}
}
// 检查登录状态
if !p.CheckLoginStatus() {
return false, "需要登录"
}
// 保存cookies
p.SaveCookies()
// 执行发布流程
steps := []struct {
name string
fn func() error
}{
{"输入标题", p.inputTitle},
{"输入内容", p.inputContent},
{"上传封面", p.uploadImage},
}
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)
}
// 点击发布
if err := p.clickPublish(); err != nil {
return false, err.Error()
}
// 等待发布结果
return p.waitForPublishResult()
}