新增发推方法
This commit is contained in:
@@ -2,17 +2,22 @@ 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"
|
||||
)
|
||||
|
||||
@@ -34,17 +39,18 @@ func (*TwitterService) Login(id int) *libs.ErrorInfo {
|
||||
// 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)
|
||||
|
||||
// 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: token,
|
||||
Value: user.Token,
|
||||
Domain: ".x.com",
|
||||
Path: "/",
|
||||
Expires: proto.TimeSinceEpoch(time.Now().Add(24 * time.Hour).Unix()), // 过期时间
|
||||
@@ -52,6 +58,7 @@ func (*TwitterService) Login(id int) *libs.ErrorInfo {
|
||||
Secure: true, // 仅 HTTPS 传输
|
||||
},
|
||||
}
|
||||
log.Println("Cookie", cookies[0].Value)
|
||||
// 全局设置
|
||||
browser.SetCookies(cookies)
|
||||
page.MustNavigate("https://x.com/home").MustWaitLoad()
|
||||
@@ -102,15 +109,73 @@ func (*TwitterService) Login(id int) *libs.ErrorInfo {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
getAccountInfo(page, int64(id))
|
||||
return libs.ErrorCode["LoginSuccessful"]
|
||||
}
|
||||
|
||||
// 获取账号信息
|
||||
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 (*TwitterService) Logout(id int) {
|
||||
Browser.CancelBrowser(id)
|
||||
}
|
||||
|
||||
// 注册账号
|
||||
func (bit *TwitterService) Register() *libs.ErrorInfo {
|
||||
// 随机获取代理
|
||||
proxyService := InitProxyService()
|
||||
@@ -278,3 +343,369 @@ func (bit *TwitterService) Register() *libs.ErrorInfo {
|
||||
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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user