新增将账号导入到adspower的方法

This commit is contained in:
zyj
2025-11-21 10:52:22 +08:00
parent ecc99977d2
commit 87a077d40c
7 changed files with 648 additions and 0 deletions

38
api/ads.go Normal file
View File

@@ -0,0 +1,38 @@
package api
import (
"go-account-register/models"
"go-account-register/services"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type AdsPower struct {
}
// 导入账号
func (a *AdsPower) ImportAccount(ctx *gin.Context) {
// 查询当前的账号列表
var twitterAccountService = services.InitTwitterAccountService()
accountList := twitterAccountService.GetAvailableImportAccount(&models.TwitterAccount{
Type: "raise",
})
// 循环创建AdsPower实例
var AdsPowerService services.AdsPowerService
log.Println("导入广告账号数量:", len(accountList))
ticker := time.NewTicker(700 * time.Millisecond)
defer ticker.Stop()
for _, account := range accountList {
<-ticker.C
AdsPowerService.CreateBrowser(account.ID)
// 如果只需要每秒10次可以限制最多处理10个
}
ctx.JSON(http.StatusBadRequest, gin.H{
"code": 200,
"msg": "成功",
"data": "",
})
}

View File

@@ -35,4 +35,6 @@ var ErrorCode = map[string]*ErrorInfo{
"AccountLocked": {Code: 0, Data: "", Msg: "账号锁定"}, "AccountLocked": {Code: 0, Data: "", Msg: "账号锁定"},
"UnlockedSuccessfully": {Code: 200, Data: "", Msg: "解锁成功"}, "UnlockedSuccessfully": {Code: 200, Data: "", Msg: "解锁成功"},
"UnlockedFailed": {Code: 0, Data: "", Msg: "解锁失败"}, "UnlockedFailed": {Code: 0, Data: "", Msg: "解锁失败"},
"ViewSuccessful": {Code: 200, Data: "", Msg: "查看成功"},
"NoPostsToView": {Code: 0, Data: "", Msg: "没有可查看的推文"},
} }

16
router/ads.go Normal file
View File

@@ -0,0 +1,16 @@
package router
import (
"go-account-register/api"
"github.com/gin-gonic/gin"
)
type AdsPower struct{}
var apiAdspower api.AdsPower
func (a *AdsPower) AdsPowerRouterInit(app *gin.Engine) {
adsPower := app.Group("/adspower")
adsPower.POST("/import_account", apiAdspower.ImportAccount)
}

View File

@@ -6,6 +6,7 @@ import (
var task Task var task Task
var twitter Twitter var twitter Twitter
var adsPower AdsPower
type Router struct { type Router struct {
} }
@@ -13,4 +14,5 @@ type Router struct {
func Init(app *gin.Engine) { func Init(app *gin.Engine) {
task.TaskRouterInit(app) task.TaskRouterInit(app)
twitter.TwitterRouterInit(app) twitter.TwitterRouterInit(app)
adsPower.AdsPowerRouterInit(app)
} }

View 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 {
}
}

View File

@@ -108,3 +108,15 @@ func (u *TwitterAccountService) Update(id int64, params *models.TwitterAccount)
func (u *TwitterAccountService) Delete(id int64) { func (u *TwitterAccountService) Delete(id int64) {
u.db.Delete(&models.TwitterAccount{}, id) 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
}

103
utils/ads_power_request.go Normal file
View File

@@ -0,0 +1,103 @@
package utils
import (
"errors"
"go-account-register/config"
"log"
"github.com/go-resty/resty/v2"
)
type AdsPowerRequest struct {
client *resty.Client
}
func NewAdsPowerRequest() *AdsPowerRequest {
appConfig, _ := config.LoadConfig()
client := resty.New()
client.SetBaseURL(appConfig.AdsPower.ApiUrl)
// 响应拦截器 (类似 axios 拦截器)
client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
if resp.StatusCode() != 200 {
log.Println("请求失败,检查网络")
return errors.New("request failed")
}
return nil
})
return &AdsPowerRequest{client: client}
}
// Request 发送通用请求
func (h *AdsPowerRequest) Request(method, url string, body interface{}, queryParams map[string]string) (*resty.Response, error) {
req := h.client.R().
SetHeader("Content-Type", "application/json")
if body != nil {
req.SetBody(body)
}
if queryParams != nil {
req.SetQueryParams(queryParams)
}
resp, err := req.Execute(method, url)
if err != nil {
log.Println("请求失败了:", err)
return nil, err
}
return resp, nil
}
/**
* 打开浏览器
*/
func (h *AdsPowerRequest) OpenBrowser(data map[string]interface{}) (*resty.Response, error) {
return h.Request("POST", "/api/v2/browser-profile/start", data, nil)
}
/**
* @description 关闭浏览器
* @param {String} id
* @returns {*resty.Response, error}
*/
func (h *AdsPowerRequest) CloseBrowser(id string) (*resty.Response, error) {
return h.Request("POST", "/api/v2/browser-profile/stop", map[string]interface{}{"profile_id": id}, nil)
}
/**
* @description 创建浏览器
* @param {Object} data
* @returns {*resty.Response, error}
*/
func (h *AdsPowerRequest) CreateBrowser(data map[string]interface{}) (*resty.Response, error) {
return h.Request("POST", "/api/v2/browser-profile/create", data, nil)
}
/**
* @description 查询浏览器运行状态
* @param {String} id
* @returns {*resty.Response, error}
*/
func (h *AdsPowerRequest) GetRunStatus(id string) (*resty.Response, error) {
return h.Request("GET", "/api/v2/browser-profile/active", nil, map[string]string{"profile_id": id})
}
/**
* @description 删除浏览器
* @param {String} id
* @returns {*resty.Response, error}
*/
func (h *AdsPowerRequest) DeleteBrowser(id string) (*resty.Response, error) {
return h.Request("POST", "/api/v2/browser-profile/delete", map[string]interface{}{"profile_id": []string{id}}, nil)
}
/**
* @description 更新浏览器配置
* @param {Object} data
* @returns {*resty.Response, error}
*/
func (h *AdsPowerRequest) UpdateBrowser(data map[string]interface{}) (*resty.Response, error) {
return h.Request("POST", "/api/v2/browser-profile/update", data, nil)
}