新增将账号导入到adspower的方法
This commit is contained in:
475
services/ads_power_service.go
Normal file
475
services/ads_power_service.go
Normal file
@@ -0,0 +1,475 @@
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -108,3 +108,15 @@ func (u *TwitterAccountService) Update(id int64, params *models.TwitterAccount)
|
||||
func (u *TwitterAccountService) Delete(id int64) {
|
||||
u.db.Delete(&models.TwitterAccount{}, id)
|
||||
}
|
||||
|
||||
// 获取可用账号
|
||||
func (u *TwitterAccountService) GetAvailableImportAccount(params *models.TwitterAccount) []models.TwitterAccount {
|
||||
var users []models.TwitterAccount
|
||||
res := u.db.Model(&models.TwitterAccount{}).Where(`(browser_id = '' OR browser_id IS NULL) AND (login_status != "账号异常" OR login_status IS NULL)`).Where(params)
|
||||
// if params.Username != "" {
|
||||
// res.Where("username LIKE ?", "%"+params.Username+"%")
|
||||
// }
|
||||
res.Order("id DESC").Find(&users)
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user