Files
go-account-register/services/ads_power_service.go
2025-11-21 10:52:22 +08:00

476 lines
14 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 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 {
}
}