geoGo/internal/publisher/dysp.go

387 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"
"os"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
)
type DouyinSpPublisher struct {
*BasePublisher
}
func NewDouyinSpPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface {
return &DouyinSpPublisher{NewBasePublisher(ctx, task, cfg, logger)}
}
func (p *DouyinSpPublisher) CheckLoginStatus() bool {
currentURL := p.GetCurrentURL()
if strings.Contains(currentURL, p.LoginedURL) {
return true
}
if strings.Contains(currentURL, p.EditorURL) {
return true
}
return false
}
func (p *DouyinSpPublisher) WaitLogin() (bool, string) {
p.LogInfo("开始等待登录...")
if err := p.SetupDriver(); err != nil {
return false, fmt.Sprintf("浏览器启动失败: %v", err)
}
defer p.Close()
p.Page.MustNavigate(p.LoginURL)
p.Sleep(2)
if p.CheckLoginStatus() {
p.SaveCookies()
return true, "already_logged_in"
}
startTime := time.Now()
timeout := 120
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 *DouyinSpPublisher) waitForEditorReady(timeout int) bool {
p.LogInfo("等待编辑器加载...")
startTime := time.Now()
for time.Since(startTime) < time.Duration(timeout)*time.Second {
uploadArea, err := p.Page.Element(".container-drag-icon")
if err == nil && uploadArea != nil {
p.LogInfo("编辑器加载完成")
return true
}
p.SleepMs(1000)
}
p.LogInfo("编辑器加载超时")
return false
}
func (p *DouyinSpPublisher) uploadVideo() error {
if p.SourcePath == "" {
return fmt.Errorf("视频不存在")
}
if _, err := os.Stat(p.SourcePath); os.IsNotExist(err) {
return fmt.Errorf("视频不存在")
}
p.LogInfo(fmt.Sprintf("开始上传视频: %s", p.SourcePath))
fileInputSelectors := []string{
"input[type='file'][accept*='video']",
".container-drag-VAfIfu input[type='file']",
"input[accept*='video']",
}
var fileInput *rod.Element
for _, selector := range fileInputSelectors {
fileInput, _ = p.Page.Element(selector)
if fileInput != nil {
p.LogInfo(fmt.Sprintf("找到文件上传输入框: %s", selector))
break
}
}
if fileInput == nil {
uploadArea, err := p.WaitForElementVisible(".container-drag-VAfIfu", 5)
if err == nil && uploadArea != nil {
p.LogInfo("点击上传区域")
p.JSClick(uploadArea)
p.SleepMs(1000)
for _, selector := range fileInputSelectors {
fileInput, _ = p.Page.Element(selector)
if fileInput != nil {
break
}
}
}
}
if fileInput == nil {
return fmt.Errorf("未找到文件上传输入框")
}
if err := fileInput.SetFiles([]string{p.SourcePath}); err != nil {
return fmt.Errorf("上传视频失败: %v", err)
}
p.LogInfo(fmt.Sprintf("视频文件已选择: %s", p.SourcePath))
return p.waitForUploadComplete()
}
func (p *DouyinSpPublisher) waitForUploadComplete() error {
p.LogInfo("等待视频上传完成...")
for i := 0; i < 300; i++ {
successElements, _ := p.Page.Elements(".upload-success, [class*='success']")
if len(successElements) > 0 {
p.LogInfo("视频上传成功")
p.SleepMs(2000)
return nil
}
errorElements, _ := p.Page.Elements(".upload-error, [class*='error']")
for _, elem := range errorElements {
visible, _ := elem.Visible()
if visible {
text, _ := elem.Text()
if text != "" {
return fmt.Errorf("上传失败: %s", text)
}
}
}
p.SleepMs(1000)
}
return fmt.Errorf("上传超时")
}
func (p *DouyinSpPublisher) inputTitle() error {
p.LogInfo("输入视频标题...")
exist, titleInput, err := p.Page.Has("input[placeholder*='标题']")
if err != nil {
return err
}
if !exist {
return fmt.Errorf("未找到标题输入框")
}
p.ClearInput(titleInput)
p.SleepMs(300)
titleInput.Input("")
p.SleepMs(300)
titleInput.Input(p.Title)
p.LogInfo(fmt.Sprintf("标题已输入: %s", p.Title))
p.SleepMs(500)
return nil
}
func (p *DouyinSpPublisher) inputDescription() error {
p.LogInfo("输入视频描述...")
descSelectors := []string{
".editor-kit-container",
".ProseMirror",
"[contenteditable='true']",
}
for _, selector := range descSelectors {
descInput, err := p.WaitForElementVisible(selector, 5)
if err == nil && descInput != nil {
p.LogInfo(fmt.Sprintf("找到描述输入框: %s", selector))
fullDescription := ""
for _, tag := range p.Tags {
fullDescription += fmt.Sprintf("#%s ", tag)
}
fullDescription = strings.TrimSpace(fullDescription)
p.SetContentEditable(descInput, fullDescription)
p.SleepMs(3000)
return nil
}
}
return fmt.Errorf("未找到描述输入框")
}
func (p *DouyinSpPublisher) clickPublish() error {
p.LogInfo("点击发布按钮...")
p.Page.Eval(`() => window.scrollTo(0, document.body.scrollHeight)`)
p.SleepMs(2000)
var publishBtn *rod.Element
popoverSpan, err := p.WaitForElementVisible("#popover-tip-container", 5)
if err == nil && popoverSpan != nil {
publishBtn, _ = popoverSpan.Element("button")
if publishBtn != nil {
text, _ := publishBtn.Text()
if text == "发布" {
p.LogInfo("通过 popover-tip-container 找到发布按钮")
}
}
}
if publishBtn == nil {
publishSelectors := []string{
"button:contains('发布')",
"button.primary-cECiOJ",
"button.primary_button",
"button[class*='primary']",
}
for _, selector := range publishSelectors {
publishBtn, _ = p.WaitForElementClickable(selector, 3)
if publishBtn != nil {
p.LogInfo(fmt.Sprintf("找到发布按钮: %s", selector))
break
}
}
}
if publishBtn == nil {
p.Page.Eval(`() => window.scrollTo(0, document.body.scrollHeight)`)
p.SleepMs(1000)
allButtons, _ := p.Page.Elements("button")
for _, btn := range allButtons {
text, _ := btn.Text()
if 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("已点击发布按钮")
p.SleepMs(3000)
return nil
}
func (p *DouyinSpPublisher) waitForPublishResult(timeout int) (bool, string) {
p.LogInfo("等待发布结果...")
startTime := time.Now()
for time.Since(startTime) < time.Duration(timeout)*time.Second {
currentURL := p.GetCurrentURL()
successKeywords := []string{"content/manage", "work-management", "success"}
for _, keyword := range successKeywords {
if strings.Contains(currentURL, keyword) {
p.LogInfo(fmt.Sprintf("发布成功URL: %s", currentURL))
return true, "发布成功"
}
}
successSelectors := []string{".semi-toast-content", ".toast-success", "[class*='success']"}
for _, selector := range successSelectors {
msgs, _ := p.Page.Elements(selector)
for _, elem := range msgs {
visible, _ := elem.Visible()
if visible {
text, _ := elem.Text()
if strings.Contains(text, "成功") || strings.Contains(text, "已发布") {
p.LogInfo(fmt.Sprintf("发布成功: %s", text))
return true, text
}
}
}
}
errorSelectors := []string{".semi-toast-content", ".toast-error", "[class*='error']"}
for _, selector := range errorSelectors {
msgs, _ := p.Page.Elements(selector)
for _, elem := range msgs {
visible, _ := elem.Visible()
if visible {
text, _ := elem.Text()
if strings.Contains(text, "失败") || strings.Contains(strings.ToLower(text), "error") {
p.LogError(fmt.Sprintf("发布失败: %s", text))
return false, text
}
}
}
}
p.SleepMs(1000)
}
return false, "发布结果未知(超时)"
}
// InitPage 初始化页面
func (p *DouyinSpPublisher) InitPage() error {
// 尝试加载cookies并检查登录状态
if err := p.LoadCookies(); err == nil {
p.Page.MustNavigate(p.LoginURL)
p.WaitForPageReady(5)
p.Sleep(4)
}
// 统一检查登录状态
if !p.CheckLoginStatus() {
p.LogInfo("未登录或登录已过期,需要重新登录")
p.DelCookies()
return fmt.Errorf("需要登录")
}
p.Page.MustNavigate(p.EditorURL)
p.WaitForPageReady(5)
p.SaveCookies()
return nil
}
func (p *DouyinSpPublisher) 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.uploadVideo},
{"输入标题", p.inputTitle},
{"输入描述", p.inputDescription},
{"点击发布", 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(120)
if success {
p.LogInfo(fmt.Sprintf("🎉 发布成功!%s", message))
return true, message
}
p.LogError(fmt.Sprintf("发布失败: %s", message))
return false, message
}
func (p *DouyinSpPublisher) LogWarning(message string) {
p.Logger.Printf("⚠️ %s", message)
}