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