geoGo/internal/publisher/shh.go

524 lines
12 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 (
"context"
"fmt"
"geo/internal/config"
"log"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
)
type SohuPublisher struct {
*BasePublisher
}
func NewSohuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface {
return &SohuPublisher{NewBasePublisher(ctx, task, cfg, logger)}
}
func (p *SohuPublisher) 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 *SohuPublisher) CheckLoginStatus() bool {
currentURL := p.GetCurrentURL()
if strings.Contains(currentURL, p.LoginURL) {
return false
}
if strings.Contains(currentURL, "clientAuth") {
return false
}
return true
}
func (p *SohuPublisher) WaitLogin() (bool, string) {
p.LogInfo("开始等待登录...")
if err := p.SetupDriver(); err != nil {
return false, fmt.Sprintf("浏览器启动失败: %v", err)
}
defer p.Close()
if p.LoginedURL != "" {
p.Page.MustNavigate(p.LoginedURL)
p.Sleep(3)
if p.CheckLoginStatus() {
p.SaveCookies()
return true, "already_logged_in"
}
}
p.Page.MustNavigate(p.LoginURL)
p.LogInfo("请扫描二维码登录...")
for i := 0; i < 120; i++ {
p.SleepMs(1000)
if p.CheckLoginStatus() {
p.SaveCookies()
p.LogInfo("登录成功")
return true, "login_success"
}
}
return false, "登录超时,请检查网络或账号状态"
}
func (p *SohuPublisher) waitForEditorReady(timeout int) bool {
p.LogInfo("等待编辑器加载...")
startTime := time.Now()
for time.Since(startTime) < time.Duration(timeout)*time.Second {
titleSelectors := []string{
".publish-title input",
"input[placeholder*='标题']",
".article-title input",
"[class*='title'] input",
}
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 *SohuPublisher) inputTitle() error {
p.LogInfo("输入文章标题...")
titleSelectors := []string{
".publish-title input",
"input[placeholder*='请输入标题']",
"input[placeholder*='标题']",
".article-title-input input",
".title-input input",
}
for _, selector := range titleSelectors {
titleInput, err := p.WaitForElementVisible(selector, 5)
if err == nil && titleInput != nil {
p.LogInfo(fmt.Sprintf("找到标题输入框: %s", selector))
if err := p.ClearInput(titleInput); err != nil {
titleInput.Input("")
}
p.SleepMs(300)
titleInput.Input(p.Title)
p.SleepMs(300)
p.LogInfo(fmt.Sprintf("标题已输入: %s", p.Title))
_, err = 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 *SohuPublisher) inputContent() error {
p.LogInfo("输入文章正文...")
if p.Content == "" {
p.LogInfo("内容为空")
return fmt.Errorf("内容为空")
}
editorSelectors := []string{
".ql-editor",
"[contenteditable='true']",
".rich-editor-content",
".editor-content",
}
var editor *rod.Element
var err error
for _, selector := range editorSelectors {
editor, err = p.WaitForElementVisible(selector, 10)
if err == nil && editor != nil {
p.LogInfo(fmt.Sprintf("找到编辑器: %s", selector))
break
}
}
if editor == nil {
return fmt.Errorf("未找到编辑器")
}
// 清空编辑器
//_, err = editor.Evaluate(&rod.EvalOptions{JS: `el => { el.innerHTML = ''; }`})
p.SleepMs(500)
// 点击编辑器获取焦点
p.JSClick(editor)
p.SleepMs(500)
// 输入内容
err = editor.Input(p.Content)
if err != nil {
return fmt.Errorf("输入内容失败: %v", err)
}
p.LogInfo(fmt.Sprintf("内容已输入,长度: %d", len(p.Content)))
return nil
}
func (p *SohuPublisher) setCover() error {
p.LogInfo("设置封面...")
if p.ImagePath == "" {
p.LogInfo("未提供封面图片,尝试使用自动封面")
return p.setAutoCover()
}
p.LogInfo(fmt.Sprintf("使用封面图片: %s", p.ImagePath))
coverSelectors := []string{
".cover-button .upload-file",
".upload-file",
"[class*='cover'] .upload-file",
".mp-upload",
}
var uploadBtn *rod.Element
var err error
for _, selector := range coverSelectors {
uploadBtn, err = p.WaitForElementClickable(selector, 5)
if err == nil && uploadBtn != nil {
p.LogInfo(fmt.Sprintf("找到上传封面按钮: %s", selector))
break
}
}
if uploadBtn == nil {
p.LogInfo("未找到上传封面按钮")
return p.setAutoCover()
}
if _, err := uploadBtn.Evaluate(&rod.EvalOptions{
JS: `el => el.scrollIntoView({block: 'center'})`,
}); err != nil {
p.LogInfo(fmt.Sprintf("滚动到按钮失败: %v", err))
}
p.SleepMs(500)
fileInputSelectors := []string{
"input[type='file'][accept*='image']",
".mp-upload input[type='file']",
".cover-button input[type='file']",
}
var fileInput *rod.Element
for _, selector := range fileInputSelectors {
fileInput, err = p.Page.Element(selector)
if err == nil && fileInput != nil {
p.LogInfo(fmt.Sprintf("找到文件上传输入框: %s", selector))
break
}
}
if fileInput == nil {
p.LogInfo("未找到文件上传输入框")
return p.setAutoCover()
}
if err := fileInput.SetFiles([]string{p.ImagePath}); err != nil {
p.LogInfo(fmt.Sprintf("上传图片失败: %v", err))
return p.setAutoCover()
}
p.LogInfo(fmt.Sprintf("封面图片已上传: %s", p.ImagePath))
p.Sleep(3)
cropConfirm, err := p.WaitForElement(".crop-btn .sure-btn, .confirm-btn", 5)
if err == nil && cropConfirm != nil {
if err := p.JSClick(cropConfirm); err == nil {
p.LogInfo("已确认裁剪")
}
p.SleepMs(1000)
}
return nil
}
func (p *SohuPublisher) setAutoCover() error {
p.LogInfo("设置为自动封面...")
autoCoverSelectors := []string{
"span:contains('自动')",
".cover-title .auto-cover",
"[class*='auto']",
}
for _, selector := range autoCoverSelectors {
autoElem, err := p.WaitForElement(selector, 3)
if err == nil && autoElem != nil {
if err := p.JSClick(autoElem); err == nil {
p.LogInfo("已选择自动封面")
p.SleepMs(500)
return nil
}
}
}
p.LogInfo("未找到自动封面选项,跳过封面设置")
return nil
}
func (p *SohuPublisher) setAbstract() error {
p.LogInfo("设置摘要...")
abstractSelectors := []string{
".abstract-main textarea",
"textarea[placeholder*='摘要']",
".abstract textarea",
}
var abstractInput *rod.Element
var err error
for _, selector := range abstractSelectors {
abstractInput, err = p.WaitForElementVisible(selector, 3)
if err == nil && abstractInput != nil {
p.LogInfo(fmt.Sprintf("找到摘要输入框: %s", selector))
break
}
}
if abstractInput != nil {
if err := abstractInput.SelectAllText(); err == nil {
abstractInput.Input("")
}
p.SleepMs(300)
abstractText := p.Content
if len(abstractText) > 120 {
abstractText = abstractText[:120]
}
abstractInput.Input(abstractText)
p.LogInfo("摘要已设置")
return nil
}
p.LogInfo("未找到摘要输入框,跳过")
return nil
}
func (p *SohuPublisher) setTags() error {
if len(p.Tags) == 0 {
p.LogInfo("无标签需要设置")
return nil
}
p.LogInfo(fmt.Sprintf("设置标签: %v", p.Tags))
tagInputSelectors := []string{
"input[placeholder*='标签']",
".tag-input input",
"[class*='tag'] input",
}
var tagInput *rod.Element
var err error
for _, selector := range tagInputSelectors {
tagInput, err = p.WaitForElement(selector, 3)
if err == nil && tagInput != nil {
p.LogInfo(fmt.Sprintf("找到标签输入框: %s", selector))
break
}
}
if tagInput != nil {
for _, tag := range p.Tags {
tagInput.Input(tag)
p.SleepMs(300)
tagInput.Input("\n")
p.SleepMs(300)
}
p.LogInfo("标签设置成功")
return nil
}
p.LogInfo("未找到标签输入框(可能无需设置)")
return nil
}
func (p *SohuPublisher) clickPublish() error {
p.LogInfo("点击发布按钮...")
publishSelectors := []string{
".publish-report-btn.active",
"button:contains('发布')",
".publish-btn",
".submit-btn",
"[class*='publish'] button",
}
var publishBtn *rod.Element
var err error
for _, selector := range publishSelectors {
publishBtn, err = p.WaitForElementClickable(selector, 5)
if err == nil && publishBtn != nil {
visible, _ := publishBtn.Visible()
if visible {
p.LogInfo(fmt.Sprintf("找到发布按钮: %s", selector))
break
}
}
}
if publishBtn == nil {
buttons, err := p.Page.Elements("button")
if err == nil {
for _, btn := range buttons {
text, _ := btn.Text()
if text == "发布" || strings.Contains(text, "发布") {
publishBtn = btn
p.LogInfo("通过遍历按钮找到发布按钮")
break
}
}
}
}
if publishBtn == nil {
return fmt.Errorf("未找到发布按钮")
}
if _, err := publishBtn.Evaluate(&rod.EvalOptions{
JS: `el => el.scrollIntoView({block: 'center', behavior: 'smooth'})`,
}); err != nil {
p.LogInfo(fmt.Sprintf("滚动到按钮失败: %v", err))
}
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("已点击发布按钮")
p.Sleep(3)
return nil
}
func (p *SohuPublisher) waitForPublishResult(timeout int) (bool, string) {
p.LogInfo("等待发布结果...")
startTime := time.Now()
for time.Since(startTime) < time.Duration(timeout)*time.Second {
currentURL := p.GetCurrentURL()
if strings.Contains(currentURL, "contentManagement") {
p.LogInfo(fmt.Sprintf("发布成功URL: %s", currentURL))
return true, "发布成功"
}
successMsgs, _ := p.Page.Elements(".el-message--success, .toast-success, .message-success, [class*='success']")
for _, elem := range successMsgs {
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, "发布结果未知(超时)"
}
// InitPage 初始化页面
func (p *SohuPublisher) InitPage() error {
// 尝试加载cookies并检查登录状态
if err := p.LoadCookies(); err == nil {
p.Page.MustNavigate(p.EditorURL)
p.WaitForPageReady(5)
p.Sleep(2)
}
// 统一检查登录状态
if !p.CheckLoginStatus() {
p.LogInfo("未登录或登录已过期,需要重新登录")
return fmt.Errorf("需要登录")
}
p.SaveCookies()
return nil
}
func (p *SohuPublisher) 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.inputTitle},
{"导入内容", p.inputContent},
}
for _, step := range steps {
if err := step.fn(); err != nil {
p.LogStep(step.name, false, err.Error())
return false, fmt.Sprintf("%s失败: %s", step.name, err.Error())
}
p.LogStep(step.name, true, "")
}
if err := p.clickPublish(); err != nil {
p.LogStep("点击发布", false, err.Error())
return false, err.Error()
}
p.LogStep("点击发布", 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
}