810 lines
27 KiB
Go
810 lines
27 KiB
Go
package services
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"go-account-register/libs"
|
||
"go-account-register/models"
|
||
"go-account-register/utils"
|
||
"log"
|
||
"math/rand"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/go-rod/rod"
|
||
"github.com/go-rod/rod/lib/cdp"
|
||
"github.com/go-rod/rod/lib/input"
|
||
"github.com/go-rod/rod/lib/proto"
|
||
)
|
||
|
||
type TwitterService struct{}
|
||
|
||
// 登录
|
||
func (*TwitterService) Login(id int) *libs.ErrorInfo {
|
||
UserService := InitTwitterAccountService()
|
||
user := UserService.GetInfo(int64(id))
|
||
obj := Browser.GetBrowser(id, &libs.Fingerprint{
|
||
Proxy: user.Proxy,
|
||
})
|
||
browser := obj.Browser
|
||
page := obj.Page
|
||
|
||
err3 := rod.Try(func() {
|
||
// 创建 Cookie 对象
|
||
// cookies := user.Cookies
|
||
// log.Println("获取cookie", cookies)
|
||
// b, _ := json.MarshalIndent(cookies, "", " ")
|
||
// log.Println(string(b))
|
||
|
||
// var token string
|
||
// for _, v := range user.Cookies {
|
||
// if v.Name == "auth_token" {
|
||
// token = v.Value
|
||
// }
|
||
// }
|
||
// log.Println("当前账号Token", token)
|
||
cookies := []*proto.NetworkCookieParam{
|
||
{
|
||
Name: "auth_token",
|
||
Value: user.Token,
|
||
Domain: ".x.com",
|
||
Path: "/",
|
||
Expires: proto.TimeSinceEpoch(time.Now().Add(24 * time.Hour).Unix()), // 过期时间
|
||
HTTPOnly: true, // 禁止 JS 访问
|
||
Secure: true, // 仅 HTTPS 传输
|
||
},
|
||
}
|
||
log.Println("Cookie", cookies[0].Value)
|
||
// 全局设置
|
||
browser.SetCookies(cookies)
|
||
page.MustNavigate("https://x.com/home").MustWaitLoad()
|
||
page.MustWaitNavigation()
|
||
})
|
||
if e, ok := err3.(*cdp.Error); ok {
|
||
if e.Code == -32000 && e.Message == "Inspected target navigated or closed" {
|
||
Browser.CancelBrowser(id)
|
||
return libs.ErrorCode["LoginFailed"]
|
||
}
|
||
}
|
||
|
||
time.Sleep(3 * time.Second)
|
||
url := page.MustEval("() => window.location.href").String()
|
||
if url == "https://x.com/account/access" {
|
||
Browser.CancelBrowser(id)
|
||
return libs.ErrorCode["AccountLocked"]
|
||
}
|
||
log.Println(url)
|
||
err1 := rod.Try(func() {
|
||
page.Timeout(5 * time.Second).MustElement(`input[autocomplete="username"]`)
|
||
})
|
||
if err1 == nil {
|
||
Browser.CancelBrowser(id)
|
||
return libs.ErrorCode["LoginFailed"]
|
||
}
|
||
// 是否检测到侧边栏
|
||
err2 := rod.Try(func() {
|
||
page.Timeout(5 * time.Second).MustElement(`a[data-testid="AppTabBar_Profile_Link"]`)
|
||
})
|
||
|
||
if err2 != nil {
|
||
Browser.CancelBrowser(id)
|
||
return libs.ErrorCode["LoginFailed"]
|
||
}
|
||
|
||
// 是否检测到帖子列表
|
||
err := rod.Try(func() {
|
||
page.Timeout(5 * time.Second).MustElement(`article[data-testid="tweet"]`)
|
||
})
|
||
|
||
if errors.Is(err, context.DeadlineExceeded) {
|
||
fmt.Println("请求空闲等待超时!")
|
||
// 关闭浏览器
|
||
Browser.CancelBrowser(id)
|
||
return libs.ErrorCode["NetworkTimeout"]
|
||
}
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
}
|
||
// getAccountInfo(page, int64(id))
|
||
// getRegisterInfo(page, int64(id), user.Password)
|
||
return libs.ErrorCode["LoginSuccessful"]
|
||
}
|
||
|
||
func (*TwitterService) GetAccountAddress(id int) *libs.ErrorInfo {
|
||
UserService := InitTwitterAccountService()
|
||
user := UserService.GetInfo(int64(id))
|
||
obj := Browser.GetBrowser(id, &libs.Fingerprint{
|
||
Proxy: user.Proxy,
|
||
})
|
||
// browser := obj.Browser
|
||
page := obj.Page
|
||
// 循环获取可用账号 到获取不到为止
|
||
is := true
|
||
for is {
|
||
account := UserService.GetRandomAccount(&models.TwitterAccount{Type: "get_address", Status: "成功"})
|
||
if account == nil {
|
||
is = false
|
||
}
|
||
rod.Try(func() {
|
||
// 进入账号信息页面
|
||
page.MustNavigate("https://x.com/" + account.Name + "/about").MustWaitLoad()
|
||
page.MustWaitNavigation()
|
||
page.MustWaitLoad() // DOM 加载完成
|
||
page.MustWaitIdle() // 基本静止(减少早期误判)
|
||
page.MustActivate() // 前置
|
||
// 获取地理位置
|
||
locationElement, err := page.Timeout(10 * time.Second).Element(`#react-root > div > div > div.css-175oi2r.r-1f2l425.r-13qz1uu.r-417010.r-18u37iz > main > div > div > div > div.css-175oi2r.r-14lw9ot.r-jxzhtn.r-1ua6aaf.r-th6na.r-1phboty.r-16y2uox.r-184en5c.r-1abdc3e.r-1lg4w6u.r-f8sm7e.r-13qz1uu.r-1ye8kvj > div > div.css-175oi2r.r-16pcm1t > div.css-175oi2r.r-1wtj0ep.r-16x9es5.r-1mmae3n.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l > div > div.css-175oi2r.r-16y2uox.r-1wbh5a2 > div:nth-child(2)`)
|
||
if err == nil {
|
||
location, _ := locationElement.Text()
|
||
UserService.Update(account.ID, &models.TwitterAccount{
|
||
Address: location,
|
||
Status: "成功",
|
||
})
|
||
} else {
|
||
UserService.Update(account.ID, &models.TwitterAccount{
|
||
Status: "失败",
|
||
})
|
||
}
|
||
time.Sleep(3 * time.Second)
|
||
})
|
||
}
|
||
return libs.ErrorCode["GetAddressSuccessful"]
|
||
}
|
||
|
||
// 获取账号信息
|
||
func getAccountInfo(page *rod.Page, id int64) {
|
||
profileButton := page.Timeout(10 * time.Second).MustElement(`a[data-testid="AppTabBar_Profile_Link"]`)
|
||
profileButton.MustClick()
|
||
// 等待新页面加载完成
|
||
page.MustWaitLoad()
|
||
res := getApiResponse(page, "/UserByScreenName")
|
||
var dataBody map[string]interface{}
|
||
json.Unmarshal([]byte(res), &dataBody)
|
||
outerMap := dataBody["data"].(map[string]interface{})
|
||
userMap := outerMap["user"].(map[string]interface{})
|
||
resultMap := userMap["result"].(map[string]interface{})
|
||
|
||
// 获取头像
|
||
avatarMap := resultMap["avatar"].(map[string]interface{})
|
||
avatarImageURL := avatarMap["image_url"].(string)
|
||
legacyMap := resultMap["legacy"].(map[string]interface{})
|
||
// 获取背景图
|
||
backdropImgUrl := legacyMap["profile_banner_url"]
|
||
var backdropUrl string
|
||
if backdropImgUrl == nil {
|
||
backdropUrl = ""
|
||
} else {
|
||
backdropUrl = legacyMap["profile_banner_url"].(string)
|
||
}
|
||
|
||
// 获取昵称
|
||
coreMap := resultMap["core"].(map[string]interface{})
|
||
nickname := coreMap["name"].(string)
|
||
screenName := coreMap["screen_name"].(string)
|
||
// 获取自我介绍
|
||
bio := legacyMap["description"].(string)
|
||
|
||
// 获取自己设置的地理位置
|
||
locationObj := resultMap["location"].(map[string]interface{})
|
||
location := locationObj["location"].(string)
|
||
// 获取粉丝数量
|
||
followersCount := legacyMap["followers_count"].(float64)
|
||
// 获取关注数量
|
||
friendsCount := legacyMap["friends_count"].(float64)
|
||
// 获取发布的帖子数量
|
||
mediaCount := legacyMap["media_count"].(float64)
|
||
log.Println("头像地址", avatarImageURL)
|
||
log.Println("背景图地址", backdropUrl)
|
||
log.Println("昵称", nickname)
|
||
log.Println("用户名", screenName)
|
||
log.Println("自我介绍", bio)
|
||
log.Println("地理位置", location)
|
||
log.Println("粉丝数量", int64(followersCount))
|
||
log.Println("关注数量", int64(friendsCount))
|
||
log.Println("发布的帖子数量", int64(mediaCount))
|
||
userService := InitTwitterAccountService()
|
||
userService.Update(id, &models.TwitterAccount{
|
||
Uid: screenName,
|
||
})
|
||
}
|
||
|
||
func getRegisterInfo(page *rod.Page, id int64, password string) {
|
||
// 进入账号信息页面
|
||
page.MustNavigate("https://x.com/settings/your_twitter_data/account").MustWaitLoad()
|
||
page.MustWaitNavigation()
|
||
page.MustWaitLoad() // DOM 加载完成
|
||
page.MustWaitIdle() // 基本静止(减少早期误判)
|
||
page.MustActivate() // 前置
|
||
// 判断当前是否需要输入密码
|
||
inpurPassword, err := page.Timeout(5 * time.Second).Element(`input[name="current_password"]`)
|
||
if err == nil {
|
||
if is, err1 := inpurPassword.Visible(); err1 == nil && is {
|
||
inpurPassword.MustInput(password)
|
||
}
|
||
ConfirmButton := page.Timeout(5 * time.Second).MustElement(`button.css-175oi2r.r-sdzlij.r-1phboty.r-rs99b7.r-lrvibr.r-ddtstp.r-1xpp3t0.r-2yi16.r-1qi8awa.r-3pj75a.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`)
|
||
ConfirmButton.MustClick()
|
||
}
|
||
time.Sleep(5 * time.Second)
|
||
// 获取创建时间
|
||
dateElement := page.Timeout(10 * time.Second).MustElement(`#react-root > div > div > div.css-175oi2r.r-1f2l425.r-13qz1uu.r-417010.r-18u37iz > main > div > div > div > section:nth-child(2) > div.css-175oi2r.r-qocrb3.r-14lw9ot.r-1h0z5md.r-1jx8gzb.r-f8sm7e.r-13qz1uu.r-1ye8kvj > div > div:nth-child(6) > div:nth-child(2)`)
|
||
createdAt, _ := dateElement.Text()
|
||
// 获取注册IP
|
||
ipElement := page.Timeout(10 * time.Second).MustElement(`#react-root > div > div > div.css-175oi2r.r-1f2l425.r-13qz1uu.r-417010.r-18u37iz > main > div > div > div > section:nth-child(2) > div.css-175oi2r.r-qocrb3.r-14lw9ot.r-1h0z5md.r-1jx8gzb.r-f8sm7e.r-13qz1uu.r-1ye8kvj > div > div:nth-child(6) > div:nth-child(3)`)
|
||
ipAddr, _ := ipElement.Text()
|
||
|
||
// 分离IP地址和地区
|
||
var ip, region string
|
||
// ipAddr 格式: "68.132.165.85 (United States)"
|
||
if strings.Contains(ipAddr, "(") {
|
||
parts := strings.Split(ipAddr, " (")
|
||
ip = strings.TrimSpace(parts[0])
|
||
if len(parts) > 1 {
|
||
region = strings.TrimRight(parts[1], ")")
|
||
}
|
||
} else {
|
||
ip = ipAddr
|
||
}
|
||
log.Println("注册IP:", ip)
|
||
log.Println("注册地区:", region)
|
||
log.Println("创建时间:", createdAt)
|
||
userService := InitTwitterAccountService()
|
||
userService.Update(id, &models.TwitterAccount{
|
||
RegisterIp: ip,
|
||
RegisterDate: createdAt,
|
||
RegisterAddress: region,
|
||
})
|
||
}
|
||
|
||
// 退出登录
|
||
func (*TwitterService) Logout(id int) {
|
||
Browser.CancelBrowser(id)
|
||
}
|
||
|
||
// 注册账号
|
||
func (bit *TwitterService) Register() *libs.ErrorInfo {
|
||
// 随机获取代理
|
||
proxyService := InitProxyService()
|
||
proxy := proxyService.GetInfo()
|
||
|
||
// 获取一个邮箱
|
||
emailService := InitEmailService()
|
||
|
||
emailInfo := emailService.GetEmailOne()
|
||
log.Println("当前邮箱信息", emailInfo)
|
||
obj := Browser.GetNewBrowser(&libs.Fingerprint{})
|
||
page := obj.Page
|
||
page.MustNavigate("https://x.com/").MustWaitLoad()
|
||
// 点击创建账号
|
||
createAccountA := page.Timeout(5 * time.Second).MustElement(`a[data-testid="signupButton"]`)
|
||
createAccountA.MustClick()
|
||
|
||
// // 点击创建账号
|
||
// createAccount := page.Timeout(5 * time.Second).MustElement(`button.css-175oi2r.r-sdzlij.r-1phboty.r-rs99b7.r-lrvibr.r-ywje51.r-184id4b.r-13qz1uu.r-2yi16.r-1qi8awa.r-3pj75a.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`)
|
||
// createAccount.MustClick()
|
||
// 判断当前是否是邮箱注册
|
||
// _, err := page.Element(`input[name="email"]`)
|
||
// if err != nil {
|
||
switchButton := page.Timeout(5 * time.Second).MustElement(`button.css-146c3p1.r-bcqeeo.r-qvutc0.r-37j5jr.r-1ff274t.r-a023e6.r-rjixqe.r-16dba41`)
|
||
switchButton.MustClick()
|
||
// }
|
||
// 获取邮箱输入框
|
||
inputEmail := page.Timeout(5 * time.Second).MustElement(`input[name="email"]`)
|
||
inputEmail.MustFocus()
|
||
inputEmail.MustInput(emailInfo.Email)
|
||
// 获取名称输入框
|
||
inputName := page.Timeout(5 * time.Second).MustElement(`input[name="name"]`)
|
||
inputName.MustFocus()
|
||
nameStr := emailInfo.Name
|
||
if emailInfo.Name == "" {
|
||
nameStr = utils.RandString(12)
|
||
}
|
||
inputName.MustInput(nameStr)
|
||
// 随机生成生日
|
||
dateParams := utils.RandDate(18, 80)
|
||
dateSelects, _ := page.Timeout(5 * time.Second).Elements(`select.r-30o5oe.r-1niwhzg.r-17gur6a.r-1yadl64.r-18jsvk2.r-1loqt21.r-1inkyih.r-rjixqe.r-crgep1.r-1wzrnnt.r-1ny4l3l.r-t60dpp.r-xd6kpl.r-is05cd.r-ttdzmv`)
|
||
log.Println(dateSelects)
|
||
fmt.Println(strconv.Itoa(dateParams.Month))
|
||
fmt.Println(strconv.Itoa(dateParams.Day))
|
||
fmt.Println(strconv.Itoa(dateParams.Year))
|
||
time.Sleep(2 * time.Second)
|
||
// 月份
|
||
dateSelects[0].WaitVisible()
|
||
opts, _ := dateSelects[0].Elements("option")
|
||
for i, opt := range opts {
|
||
txt, _ := opt.Text()
|
||
val, _ := opt.Attribute("value")
|
||
log.Printf("option[%d] text=%s value=%s\n", i, txt, *val)
|
||
if i != 0 && i == dateParams.Month {
|
||
dateSelects[0].MustSelect(txt)
|
||
}
|
||
}
|
||
// 年份
|
||
dateSelects[2].WaitVisible()
|
||
dateSelects[2].MustSelect(strconv.Itoa(dateParams.Year))
|
||
time.Sleep(2 * time.Second)
|
||
// 日期
|
||
dateSelects[1].WaitVisible()
|
||
dateSelects[1].MustSelect(strconv.Itoa(dateParams.Day))
|
||
// dateSelects[0].MustClick()
|
||
// monthOption := dateSelects[0].MustElement(fmt.Sprintf(`option[value="%d"]`, dateParams.Month))
|
||
// monthOption.MustClick()
|
||
time.Sleep(2 * time.Second)
|
||
// 点击下一步
|
||
ocfSignupNextLink := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ocfSignupNextLink"]`)
|
||
ocfSignupNextLink.MustClick()
|
||
// 判断是否需要验证码
|
||
time.Sleep(4 * time.Second)
|
||
_, err1 := page.Timeout(5 * time.Second).Element(`input[name="verfication_code"]`)
|
||
if err1 != nil {
|
||
// res := getApiResponse(page, "")
|
||
}
|
||
//TODO 对接获取邮箱信息接口
|
||
time.Sleep(10 * time.Second)
|
||
codeValue := LoopGetEmailCode("pop.dragonsmail.com:993", emailInfo.Email, emailInfo.Password)
|
||
if codeValue == "0" {
|
||
emailService.Update(emailInfo.Email, models.Email{
|
||
Status: 0,
|
||
})
|
||
return libs.ErrorCode["MailboxUnavailable"]
|
||
}
|
||
// 获取邮箱验证码
|
||
log.Println("获取当前验证码", codeValue)
|
||
codeInput1, _ := page.Timeout(5 * time.Second).Element(`input[name="verfication_code"]`)
|
||
codeInput1.MustInput(codeValue)
|
||
time.Sleep(2 * time.Second)
|
||
// 点击下一步
|
||
codeNextButton := page.Timeout(5 * time.Second).MustElement(`button.css-175oi2r.r-sdzlij.r-1phboty.r-rs99b7.r-lrvibr.r-19yznuf.r-64el8z.r-1fkl15p.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`)
|
||
codeNextButton.MustClick()
|
||
// // 点击验证按钮
|
||
// verifyButton := page.Timeout(5 * time.Second).MustElement(`button[data-theme="home.verifyButton"]`)
|
||
// verifyButton.MustClick()
|
||
// 输入密码
|
||
passwordInput, err2 := page.Timeout(5 * time.Second).Element(`input[name="password"]`)
|
||
if err2 != nil {
|
||
// res := getApiResponse(page, "")
|
||
}
|
||
|
||
passwordInput.MustInput(emailInfo.Password)
|
||
time.Sleep(5 * time.Second)
|
||
// 点击下一步
|
||
loginButton := page.Timeout(5 * time.Second).MustElement(`button[data-testid="LoginForm_Login_Button"]`)
|
||
loginButton.MustClick()
|
||
time.Sleep(5 * time.Second)
|
||
page.MustNavigate("https://x.com").MustWaitLoad()
|
||
time.Sleep(10 * time.Second)
|
||
// 获取cookie
|
||
cookieArr := GetPageCookie(page, "https://x.com")
|
||
// 创建用户
|
||
twitterAccountService := InitTwitterAccountService()
|
||
twitterAccountService.Create(&models.TwitterAccount{
|
||
Name: nameStr,
|
||
Email: emailInfo.Email,
|
||
Password: emailInfo.Password,
|
||
Birthday: strconv.Itoa(dateParams.Year) + "-" + strconv.Itoa(dateParams.Month) + "-" + strconv.Itoa(dateParams.Day),
|
||
Cookies: cookieArr,
|
||
Status: "注册成功",
|
||
LoginStatus: "登录成功",
|
||
Proxy: func() string {
|
||
if proxy != nil && proxy.Type != "" {
|
||
return proxy.Type + "://" + proxy.Proxy
|
||
}
|
||
return ""
|
||
}(),
|
||
})
|
||
emailService.Update(emailInfo.Email, models.Email{
|
||
UseStatus: 1,
|
||
})
|
||
// 设置fa2
|
||
page.MustNavigate("https://x.com/settings/account/login_verification").MustWaitLoad()
|
||
// 点击fa2按钮
|
||
inputCheckbox := page.Timeout(5 * time.Second).MustElement(`input[aria-describedby="CHECKBOX_2_LABEL"]`)
|
||
inputCheckbox.MustClick()
|
||
// 等待2秒
|
||
time.Sleep(2 * time.Second)
|
||
// 设置密码
|
||
inpurPassword := page.Timeout(5 * time.Second).MustElement(`input[name="password"]`)
|
||
inpurPassword.MustInput(emailInfo.Password)
|
||
// 点击确认
|
||
faConfirm := page.Timeout(5 * time.Second).MustElement(`button[data-testid="LoginForm_Login_Button"]`)
|
||
faConfirm.MustClick()
|
||
// 等待2秒
|
||
time.Sleep(2 * time.Second)
|
||
ActionListNextButton := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ActionListNextButton"]`)
|
||
ActionListNextButton.MustClick()
|
||
// 点击无法扫描二维码
|
||
queryCode := page.Timeout(5 * time.Second).MustElement("div.css-175oi2r.r-1awozwy.r-w7s2jr>div.css-175oi2r.r-95jzfe>div.css-146c3p1.r-bcqeeo.r-1ttztb7.r-qvutc0.r-37j5jr.r-a023e6.r-rjixqe.r-16dba41>button.css-1jxf684.r-bcqeeo.r-qvutc0.r-poiln3.r-fdjqy7")
|
||
queryCode.MustClick()
|
||
fa2Code := page.Timeout(5 * time.Second).MustElement(`div.css-146c3p1.r-bcqeeo.r-qvutc0.r-37j5jr.r-1blvdjr.r-vrz42v.r-b88u0q.r-x572qd.r-ywje51.r-1oqcu8e.r-q4m81j.r-13qz1uu`)
|
||
fa2CodeText := fa2Code.MustText()
|
||
log.Println("当前账号的二步验证码,", fa2CodeText)
|
||
ocfShowCodeNextLink := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ocfShowCodeNextLink"]`)
|
||
ocfShowCodeNextLink.MustClick()
|
||
codeText, _ := utils.GetFA2Code(fa2CodeText)
|
||
ocfEnterTextTextInput := page.Timeout(5 * time.Second).MustElement(`input[data-testid="ocfEnterTextTextInput"]`)
|
||
ocfEnterTextTextInput.MustInput(codeText)
|
||
ocfEnterTextNextButton := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ocfEnterTextNextButton"]`)
|
||
ocfEnterTextNextButton.MustClick()
|
||
// 删除浏览器
|
||
return libs.ErrorCode["RegisterSuccessful"]
|
||
|
||
}
|
||
|
||
// 发推
|
||
func (*TwitterService) 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.GetBrowser(accountId, &libs.Fingerprint{
|
||
Proxy: user.Proxy,
|
||
})
|
||
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 postButton(page *rod.Page) int {
|
||
// 定义一个通道接收消息
|
||
router := page.HijackRequests()
|
||
defer router.MustStop()
|
||
ch := make(chan int, 1)
|
||
router.MustAdd("*/CreateTweet*", func(ctx *rod.Hijack) {
|
||
ctx.ContinueRequest(&proto.FetchContinueRequest{})
|
||
ch <- ctx.Response.Payload().ResponseCode
|
||
})
|
||
|
||
go router.Run()
|
||
// 点击发布按钮
|
||
postButton := page.Timeout(10 * time.Second).MustElement(`button[data-testid="tweetButtonInline"]`)
|
||
postButton.MustClick()
|
||
select {
|
||
case res := <-ch:
|
||
return res
|
||
case <-time.After(30 * time.Second):
|
||
log.Println("等待上传响应超时")
|
||
return 500
|
||
}
|
||
}
|
||
|
||
// 搜索
|
||
func (*TwitterService) Search(accountId int, keyword string, twitterId string) *libs.ErrorInfo {
|
||
// 获取账号状态
|
||
UserService := InitTwitterAccountService()
|
||
user := UserService.GetInfo(int64(accountId))
|
||
if user.LoginStatus != "登录成功" {
|
||
return libs.ErrorCode["AccountHasBeenTakenOffline"]
|
||
}
|
||
// 获取实例
|
||
obj := Browser.GetBrowser(accountId, &libs.Fingerprint{
|
||
Proxy: user.Proxy,
|
||
})
|
||
page := obj.Page
|
||
// 跳转到首页
|
||
page.MustNavigate("https://x.com/home").MustWaitLoad()
|
||
err2 := rod.Try(func() {
|
||
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()
|
||
})
|
||
if err2 != nil {
|
||
}
|
||
|
||
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"]
|
||
}
|
||
|
||
page.MustNavigate("https://x.com/explore").MustWaitLoad()
|
||
|
||
searchInput := page.Timeout(10 * time.Second).MustElement(`input[data-testid="SearchBox_Search_Input"]`)
|
||
// 聚焦
|
||
searchInput.Focus()
|
||
searchInput.MustInput(keyword)
|
||
|
||
searchCode := waitSearch(page)
|
||
if searchCode != 200 {
|
||
return libs.ErrorCode["SearchFailed"]
|
||
}
|
||
peopleButton := page.Timeout(10 * time.Second).MustElement(`div[data-testid="ScrollSnap-List"] div.css-175oi2r.r-14tvyh0.r-cpa5s6.r-16y2uox:nth-child(3)`)
|
||
peopleButton.MustClick()
|
||
|
||
retry, err1 := page.Timeout(5 * time.Second).Element(`div.css-175oi2r.r-1awozwy.r-16y2uox.r-1777fci.r-dd0y9b.r-3o4zer.r-f8sm7e.r-13qz1uu.r-1ye8kvj`)
|
||
if retry != nil || err1 == nil {
|
||
return libs.ErrorCode["SearchFailed"]
|
||
}
|
||
|
||
var isSearch bool = true
|
||
var searchCount int = 0
|
||
var activeUser *rod.Element = nil
|
||
var nameArr []string = []string{}
|
||
var nameBool map[string]bool = make(map[string]bool)
|
||
for isSearch && searchCount < 10 {
|
||
searchCount++
|
||
// 获取视口内的所有元素
|
||
ids, err2 := getVisibleElements(page, `div.css-175oi2r.r-f8sm7e.r-13qz1uu.r-1ye8kvj div.css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-bcqeeo.r-1ttztb7.r-qvutc0.r-37j5jr.r-a023e6.r-rjixqe.r-16dba41.r-18u37iz.r-1wvb978`)
|
||
if err2 != nil || len(ids) <= 0 {
|
||
return libs.ErrorCode["SearchFailed"]
|
||
}
|
||
for _, v := range ids {
|
||
text := v.MustText()
|
||
if !nameBool[text] {
|
||
nameBool[text] = true
|
||
nameArr = append(nameArr, text)
|
||
}
|
||
if text == "@"+twitterId {
|
||
activeUser = v
|
||
break
|
||
}
|
||
}
|
||
if activeUser != nil {
|
||
isSearch = false
|
||
log.Println("循环:")
|
||
log.Println(searchCount)
|
||
log.Println(len(nameArr))
|
||
log.Println("找到用户")
|
||
} else {
|
||
log.Println("开始滚动")
|
||
scrollElementLocationTop(ids[len(ids)-1])
|
||
time.Sleep(5 * time.Second)
|
||
}
|
||
}
|
||
|
||
if searchCount >= 10 {
|
||
return libs.ErrorCode["SearchFailed"]
|
||
}
|
||
time.Sleep(1 * time.Second)
|
||
activeUser.MustClick()
|
||
// 等待5-8秒
|
||
s := 5 + rand.Intn(3)
|
||
time.Sleep(time.Duration(s) * time.Second)
|
||
posts := page.Timeout(10 * time.Second).MustElements(`div.css-175oi2r.r-j5o65s.r-qklmqi.r-1adg3ll.r-1ny4l3l`)
|
||
var postArr []*rod.Element
|
||
for _, post := range posts {
|
||
aid := post.Timeout(10 * time.Second).MustElement(`div.css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-bcqeeo.r-1ttztb7.r-qvutc0.r-37j5jr.r-a023e6.r-rjixqe.r-16dba41.r-18u37iz.r-1wvb978`)
|
||
text := aid.MustText()
|
||
if text == "@"+twitterId {
|
||
postArr = append(postArr, post)
|
||
}
|
||
}
|
||
if len(postArr) <= 0 {
|
||
return libs.ErrorCode["SearchSuccessful"]
|
||
}
|
||
// 随机选择一个
|
||
activePost := postArr[rand.Intn(len(postArr))]
|
||
activePost.MustScrollIntoView() // 将元素滑动到页面中间
|
||
tweetText := activePost.Timeout(10 * time.Second).MustElement(`div[data-testid="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"]`},
|
||
})
|
||
// 等待15-20秒
|
||
s2 := 15 + rand.Intn(10)
|
||
time.Sleep(time.Duration(s2) * time.Second)
|
||
// 返回上一页
|
||
// 模拟点击后退按钮
|
||
page.MustNavigateBack()
|
||
// 等待新页面加载完成
|
||
page.MustWaitLoad()
|
||
// 等待5-8秒
|
||
time.Sleep(time.Duration(s) * time.Second)
|
||
return libs.ErrorCode["SearchSuccessful"]
|
||
}
|
||
|
||
// 获取视口内可见的指定元素
|
||
func getVisibleElements(page *rod.Page, selector string) ([]*rod.Element, error) {
|
||
// 1. 获取所有候选元素
|
||
elements, err := page.Elements(selector)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 2. 过滤出视口内可见的元素
|
||
var visibleElements []*rod.Element
|
||
for _, el := range elements {
|
||
isVisible, err := el.Eval(`function() {
|
||
const rect = this.getBoundingClientRect();
|
||
const viewportHeight = window.innerHeight;
|
||
const viewportWidth = window.innerWidth;
|
||
|
||
// 判断元素是否至少部分在视口内
|
||
return (
|
||
rect.top < viewportHeight &&
|
||
rect.bottom > 0 &&
|
||
rect.left < viewportWidth &&
|
||
rect.right > 0
|
||
);
|
||
}`)
|
||
if err == nil && isVisible.Value.Bool() {
|
||
visibleElements = append(visibleElements, el)
|
||
}
|
||
}
|
||
|
||
return visibleElements, nil
|
||
}
|
||
|
||
// 将指定元素滑动到顶部
|
||
func scrollElementLocationTop(el *rod.Element) {
|
||
// 执行JS:滚动到视口顶部(平滑滚动)
|
||
el.MustEval(`function() {
|
||
this.scrollIntoView({
|
||
behavior: "smooth",
|
||
block: "start" // 顶部对齐
|
||
});
|
||
}`)
|
||
|
||
// 等待滚动完成
|
||
// time.Sleep(2 * time.Second)
|
||
}
|
||
|
||
// 搜索按钮点击之后监听请求
|
||
func waitSearch(page *rod.Page) int {
|
||
// 定义一个通道接收消息
|
||
router := page.HijackRequests()
|
||
defer router.MustStop()
|
||
ch := make(chan int, 1)
|
||
router.MustAdd("*/SearchTimeline*", func(ctx *rod.Hijack) {
|
||
ctx.ContinueRequest(&proto.FetchContinueRequest{})
|
||
ch <- ctx.Response.Payload().ResponseCode
|
||
})
|
||
|
||
go router.Run()
|
||
// 点击搜索按钮
|
||
searchInput := page.Timeout(10 * time.Second).MustElement(`input[data-testid="SearchBox_Search_Input"]`)
|
||
searchInput.MustKeyActions().Press(input.Enter).MustDo()
|
||
select {
|
||
case res := <-ch:
|
||
return res
|
||
case <-time.After(30 * time.Second):
|
||
log.Println("等待上传响应超时")
|
||
return 500
|
||
}
|
||
}
|
||
|
||
// 获取指定接口的响应参数
|
||
func getApiResponse(page *rod.Page, url string) string {
|
||
// 定义一个通道接收消息
|
||
ch := make(chan string)
|
||
go page.EachEvent(func(e *proto.PageLoadEventFired) {
|
||
log.Println("1234567890")
|
||
}, func(e *proto.NetworkResponseReceived) {
|
||
if strings.Contains(e.Response.URL, url) {
|
||
reply, err := (proto.NetworkGetResponseBody{RequestID: e.RequestID}).Call(page)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
}
|
||
ch <- reply.Body
|
||
// fmt.Println(reply.Body)
|
||
}
|
||
})()
|
||
|
||
select {
|
||
case res := <-ch:
|
||
return res
|
||
case <-time.After(30 * time.Second):
|
||
log.Println("等待上传响应超时")
|
||
return ""
|
||
}
|
||
}
|
||
|
||
func (*TwitterService) LoopViewPost(id int) *libs.ErrorInfo {
|
||
UserService := InitTwitterAccountService()
|
||
user := UserService.GetInfo(int64(id))
|
||
obj := Browser.GetBrowser(id, &libs.Fingerprint{
|
||
Proxy: user.Proxy,
|
||
})
|
||
page := obj.Page
|
||
// 获取推文列表
|
||
postList := InitTwitterPostService().GetAll()
|
||
if len(postList) <= 0 {
|
||
return libs.ErrorCode["NoPostsToView"]
|
||
}
|
||
for _, v := range postList {
|
||
page.MustNavigate(v.Link).MustWaitLoad()
|
||
page.MustWaitNavigation()
|
||
page.MustWaitLoad() // DOM 加载完成
|
||
page.MustWaitIdle() // 基本静止(减少早期误判)
|
||
page.MustActivate() // 前置
|
||
// 等待5-10秒
|
||
s := 5 + rand.Intn(5)
|
||
time.Sleep(time.Duration(s) * time.Second)
|
||
log.Println("浏览推文链接:", v.Link)
|
||
|
||
if v.ContentLink != "" {
|
||
page.MustNavigate(v.ContentLink).MustWaitLoad()
|
||
page.MustWaitNavigation()
|
||
page.MustWaitLoad() // DOM 加载完成
|
||
page.MustWaitIdle() // 基本静止(减少早期误判)
|
||
page.MustActivate() // 前置
|
||
// 等待5-10秒
|
||
s2 := 5 + rand.Intn(5)
|
||
time.Sleep(time.Duration(s2) * time.Second)
|
||
log.Println("浏览推文内容链接:", v.ContentLink)
|
||
}
|
||
|
||
}
|
||
return libs.ErrorCode["ViewSuccessful"]
|
||
}
|
||
|
||
// 导出 Twitter 账号数据到 Excel
|
||
func (s *TwitterService) ExportAccounts() (code int, msg string, files []string) {
|
||
result, err := ExportTwitterAccounts()
|
||
if err != nil {
|
||
return 500, "导出失败", nil
|
||
}
|
||
return 200, "导出成功", result
|
||
}
|