package services import ( "context" "encoding/json" "errors" "fmt" "go-account-register/config" "go-account-register/libs" "go-account-register/models" "go-account-register/utils" "log" "math/rand" "os" "strings" "time" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/proto" ) type AdsPowerService struct { } func (*AdsPowerService) CreateBrowser(accountId int64) string { TwitterAccount := InitTwitterAccountService() user := TwitterAccount.GetInfo(accountId) fingerprint := map[string]interface{}{ "name": fmt.Sprintf(`推特账号%d`, accountId), "group_id": "0", "platform": "", "user_proxy_config": map[string]interface{}{ "proxy_soft": "no_proxy", "proxy_type": "socks5", }, "fingerprint_config": map[string]interface{}{ "webrtc": "proxy", "random_ua": map[string]interface{}{ "ua_browser": []string{ "chrome", }, "ua_system_version": []string{ "Windows 10", "Windows 11", "Mac OS X 15", "Mac OS X 14", }, }, }, } if user.Cookies != nil { fingerprint["cookie"] = user.Cookies } else { cookieObj := []map[string]interface{}{ { "name": "auth_token", "value": user.Token, "domain": ".x.com", "path": "/", "httpOnly": false, "secure": true, "session": true, "expires": proto.TimeSinceEpoch(time.Now().Add(24 * time.Hour).Unix()), "sameSite": "unspecified", }, } cookieJson, _ := json.Marshal(cookieObj) fingerprint["cookie"] = string(cookieJson) } if user.Proxy != "" { var proxyHandle utils.ProxyHandle protocol, username, password, ip, port, err2 := proxyHandle.ParseProxy(user.Proxy) if err2 == nil { fingerprint["user_proxy_config"] = map[string]interface{}{ "proxy_soft": "other", "proxy_type": protocol, "proxy_host": ip, "proxy_port": port, "proxy_user": username, "proxy_password": password, } } } adsPowerRequest := utils.NewAdsPowerRequest() jsonStr, _ := adsPowerRequest.CreateBrowser(fingerprint) var rawData map[string]interface{} if err := json.Unmarshal([]byte(jsonStr.Body()), &rawData); err != nil { panic(err) } log.Println("创建浏览器返回数据", rawData) success := rawData["msg"].(string) if success == "Success" { // 如果成功则将数据存入数据库 data := rawData["data"].(map[string]interface{}) id := data["profile_id"].(string) // jsonData, _ := json.Marshal(data) TwitterAccount.Update(accountId, &models.TwitterAccount{ BrowserId: id, }) return id } return "" } func (*AdsPowerService) GetRunStatus(id string) int { adsPowerRequest := utils.NewAdsPowerRequest() jsonStr, _ := adsPowerRequest.GetRunStatus(id) var rawData map[string]interface{} if err := json.Unmarshal([]byte(jsonStr.Body()), &rawData); err != nil { panic(err) } success := rawData["msg"].(string) if success == "success" { data := rawData["data"].(map[string]interface{}) status := data["status"].(string) if status == "Active" { return 1 } return 0 } return 0 } // 打开浏览器 func (*AdsPowerService) OpenBrowser(accountId int, browserId string) string { // 获取当前 adsPowerRequest := utils.NewAdsPowerRequest() data := map[string]interface{}{ "profile_id": browserId, "delete_cache": "1", // "last_opened_tabs": "0", } jsonStr, _ := adsPowerRequest.OpenBrowser(data) var rawData map[string]interface{} if err := json.Unmarshal([]byte(jsonStr.Body()), &rawData); err != nil { panic(err) } success := rawData["msg"].(string) if success == "success" { data := rawData["data"].(map[string]interface{}) ws := data["ws"].(map[string]interface{}) puppeteer := ws["puppeteer"].(string) TwitterAccount := InitTwitterAccountService() TwitterAccount.Update(int64(accountId), &models.TwitterAccount{ BrowserUrl: puppeteer, }) return puppeteer } return "" } // 删除浏览器 func (*AdsPowerService) DeleteBrowser(browserId string) bool { adsPowerRequest := utils.NewAdsPowerRequest() // 先关闭浏览器 _, err := adsPowerRequest.CloseBrowser(browserId) if err != nil { log.Println("关闭浏览器失败") } // 删除浏览器 _, err2 := adsPowerRequest.DeleteBrowser(browserId) log.Println("删除浏览器,", err2 == nil) return err2 == nil } // 关闭浏览器 func (*AdsPowerService) CloseBrowser(browserId string) bool { adsPowerRequest := utils.NewAdsPowerRequest() jsonStr, _ := adsPowerRequest.CloseBrowser(browserId) var result struct { Data string `json:"data"` // 动态键名 → 值 Success bool `json:"success"` } if err := json.Unmarshal([]byte(jsonStr.Body()), &result); err != nil { return false } return true } func randNum(min, max int) int { return rand.Intn(max-min) + min } func getKey(id string) input.Key { keys := map[string]input.Key{ "0": input.Numpad0, "1": input.Numpad1, "2": input.Numpad2, "3": input.Numpad3, "4": input.Numpad4, "5": input.Numpad5, "6": input.Numpad6, "7": input.Numpad7, "8": input.Numpad8, "9": input.Numpad9, "+": input.NumpadAdd, } return keys[id] } // 将指定元素滑动到顶部 func scrollElementLocationCenter(el *rod.Element) { // 执行JS:滚动到视口顶部(平滑滚动) el.MustEval(`function() { this.scrollIntoView({ behavior: "smooth", block: "center", // 关键参数:垂直居中 inline: "center" // 水平居中 }); }`) // 等待滚动完成 // time.Sleep(2 * time.Second) } // 发推 func (*AdsPowerService) SendPost(accountId int, Content string, Img []string) *libs.ErrorInfo { // 获取账号状态 UserService := InitTwitterAccountService() user := UserService.GetInfo(int64(accountId)) if user.LoginStatus != "登录成功" { return libs.ErrorCode["AccountHasBeenTakenOffline"] } // 获取实例 obj := Browser.GetBitBrowser(user.BrowserUrl) page := obj.Page // 跳转到首页 page.MustNavigate("https://x.com/home").MustWaitLoad() err := rod.Try(func() { // 获取内容输入框 textInput := page.Timeout(10 * time.Second).MustElement(`div.notranslate.public-DraftEditor-content`) textInput.MustInput(Content) // 检测字符串是否包含标签 if strings.Contains(Content, "#") { _, err := page.Timeout(5 * time.Second).Element(`div.css-175oi2r.r-14lw9ot.r-z2wwpe.r-1upvrn0.r-h3f8nf.r-11yh6sk.r-1rnoaur`) time.Sleep(2 * time.Second) if err == nil { tagBuuton := page.Timeout(5 * time.Second).MustElement(`div.css-175oi2r.r-14lw9ot.r-z2wwpe.r-1upvrn0.r-h3f8nf.r-11yh6sk.r-1rnoaur div[data-testid="typeaheadResult"] button.css-175oi2r.r-x572qd.r-6dt33c.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`) tagBuuton.MustClick() time.Sleep(2 * time.Second) } } // 循环保存图片到本地 if len(Img) > 0 { var file libs.File for _, v := range Img { tempFile, _ := file.DownloadImage(v) // 结束时删除临时文件 defer os.Remove(tempFile) page.MustElement(`input[data-testid="fileInput"]`).MustSetFiles(tempFile) time.Sleep(1 * time.Second) } time.Sleep(5 * time.Second) } }) if errors.Is(err, context.DeadlineExceeded) { fmt.Println("请求空闲等待超时!") return libs.ErrorCode["SendPostFailed"] } code := postButton(page) if code == 200 { return libs.ErrorCode["SendPostSuccessful"] } return libs.ErrorCode["SendPostFailed"] } // 模拟人类刷帖子 限制时间 func (*AdsPowerService) ViewPostsSpecifiedTime(accountId int) *libs.ErrorInfo { UserService := InitTwitterAccountService() user := UserService.GetInfo(int64(accountId)) if user.LoginStatus != "登录成功" { return libs.ErrorCode["AccountHasBeenTakenOffline"] } // 获取实例 obj := Browser.GetBitBrowser(user.BrowserUrl) page := obj.Page // 跳转到首页 page.MustNavigate("https://x.com/home").MustWaitLoad() forYou := page.Timeout(10 * time.Second).MustElement(`div[data-testid="ScrollSnap-List"] div.css-175oi2r.r-14tvyh0.r-cpa5s6.r-16y2uox:nth-child(1)`) forYou.MustClick() page.MustWaitNavigation() // 是否检测到帖子列表 err := rod.Try(func() { page.Timeout(5 * time.Second).MustElement(`article[data-testid="tweet"]`) }) if errors.Is(err, context.DeadlineExceeded) { fmt.Println("请求空闲等待超时!") // 关闭浏览器 Browser.CancelBrowser(accountId) return libs.ErrorCode["NetworkTimeout"] } var status chan string = make(chan string) // 获取配置中的时间限制 appConfig, _ := config.LoadConfig() randTime := appConfig.Limit.ViweTimeMin + rand.Intn(appConfig.Limit.ViweTimeMax-appConfig.Limit.ViweTimeMin) go loopViewPosts(page, accountId, status, randTime) select { case res := <-status: if res == "fali" { return libs.ErrorCode["MaintainAccountFailed"] } if res == "success" { return libs.ErrorCode["MaintainAccountSuccessful"] } case <-time.After(time.Duration(randTime) * time.Minute): return libs.ErrorCode["MaintainAccountSuccessful"] } return libs.ErrorCode["MaintainAccountSuccessful"] // // 获取配置中的时间限制 // appConfig, _ := config.LoadConfig() // randTime := appConfig.Limit.ViweTimeMin + rand.Intn(appConfig.Limit.ViweTimeMax-appConfig.Limit.ViweTimeMin) // // 获取当前时间戳 // startTime := time.Now().Unix() // var activeTime int64 = time.Now().Unix() // // 函数报错则向这个通道发送消息 // ch := make(chan string, 1) // for activeTime-startTime < int64(randTime*60) { // select { // case <-ch: // // 失败 // return libs.ErrorCode["MaintainAccountFailed"] // default: // viewPosts(page, ch, accountId) // } // activeTime = time.Now().Unix() // } // return libs.ErrorCode["MaintainAccountSuccessful"] // timer := time.NewTimer(time.Duration(randTime) * time.Minute) // defer timer.Stop() // ticker := time.NewTicker(1 * time.Second) // defer ticker.Stop() // for { // select { // case <-timer.C: // fmt.Println("30分钟已到,自动返回") // return libs.ErrorCode["MaintainAccountSuccessful"] // case <-ch: // // 失败 // return libs.ErrorCode["MaintainAccountFailed"] // case <-ticker.C: // viewPosts(page, ch, accountId) // } // } } func loopViewPosts(page *rod.Page, accountId int, status chan string, randTime int) { // 获取当前时间戳 startTime := time.Now().Unix() var activeTime int64 = time.Now().Unix() // 函数报错则向这个通道发送消息 ch := make(chan string, 1) for activeTime-startTime < int64(randTime*60) { viewPosts(page, ch, accountId) activeTime = time.Now().Unix() } status <- "success" } // 查看帖子 func viewPosts(page *rod.Page, ch chan string, accountId int) bool { // 滑动滚动条 log.Println("开始滚动", accountId) randomTrackScroll(page) log.Println("结束滚动", accountId) is := rand.Float64() // 生成随机数 if is < 0.5 { err := rod.Try(func() { // 获取视口内的帖子列表 list, err := getVisibleElements(page, `div[data-testid="tweetText"]`) if err != nil || len(list) <= 0 { return } // 等待两秒 time.Sleep(2 * time.Second) // 随机选择一个点击进入帖子 tweetText := list[rand.Intn(len(list))] log.Println("选择一个帖子:", accountId, tweetText) scrollElementLocationCenter(tweetText) // 禁用链接点击 _, err3 := page.Evaluate(&rod.EvalOptions{ JS: `(selector) => { const el = document.querySelector(selector); const links = el.querySelectorAll('a'); links.forEach(a => { a.style.pointerEvents = 'none'; }); }`, JSArgs: []interface{}{`div[data-testid="tweetText"]`}, }) if err3 != nil { } tweetText.MustClick() // 恢复链接的点击行为(如果需要) _, _ = page.Evaluate(&rod.EvalOptions{ JS: `(selector) => { const el = document.querySelector(selector); const links = el.querySelectorAll('a'); links.forEach(a => { a.style.pointerEvents = ''; }); }`, JSArgs: []interface{}{`div[data-testid="tweetText"]`}, }) page.MustWaitNavigation() // 查看帖子 3-5分钟 timeMin := 3 + rand.Intn(2) time.Sleep(time.Duration(timeMin) * time.Minute) // 退回到上一步 // page.MustNavigateBack() // 等待新页面加载完成 // page.MustWaitLoad() page.MustNavigate("https://x.com/home").MustWaitLoad() // 判断当前URL是否与预期的URL一致 url := page.MustEval("() => window.location.href").String() if url != "https://x.com/home" { // 如果不一致 就直接跳转 page.MustNavigate("https://x.com/home").MustWaitLoad() } }) if err != nil { ch <- "error" } return true } return false } // 随机滚动 func randomTrackScroll(page *rod.Page) { // height, _ := page.Eval(`() => document.body.scrollHeight`) // totalHeight := int(height.Value.Num()) err := rod.Try(func() { var totalHeight int = 1000 + rand.Intn(1000) currentPos := 0 log.Println("获取随机滚动的参数:", totalHeight) for currentPos < totalHeight { // 随机水平偏移(模拟人类不精确的滑动) xOffset := rand.Intn(20) - 10 // -10到10之间的随机值 yDistance := 50 + rand.Intn(150) err := page.Mouse.Scroll(float64(xOffset), float64(yDistance), 1) if err != nil { log.Printf("滚动失败: %v", err) } currentPos += yDistance // 随机等待和偶尔的回滑 if rand.Float32() < 0.1 { // 10%概率回滑一点 backStep := 10 + rand.Intn(30) err := page.Mouse.Scroll(0, -float64(backStep), 1) if err != nil { log.Printf("回滑失败: %v", err) } currentPos -= backStep } time.Sleep(time.Millisecond * time.Duration(50+rand.Intn(300))) log.Println("滚动完成") } }) if err != nil { } }