Skip to content

Commit

Permalink
Revert "feat(twitter): implement account rotation and rate limit hand…
Browse files Browse the repository at this point in the history
…ling"

This reverts commit b9d936f.
  • Loading branch information
teslashibe committed Oct 3, 2024
1 parent b9d936f commit 33b942a
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 265 deletions.
102 changes: 20 additions & 82 deletions pkg/scrapers/twitter/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,98 +2,35 @@ package twitter

import (
"fmt"
"sync"
"time"

"github.com/masa-finance/masa-oracle/pkg/config"
twitterscraper "github.com/masa-finance/masa-twitter-scraper"
"github.com/sirupsen/logrus"
)

type TwitterAccount struct {
Username string
Password string
TwoFACode string
RateLimitedUntil time.Time
}

type TwitterAccountManager struct {
accounts []*TwitterAccount
index int
mutex sync.Mutex
}

func NewTwitterAccountManager(accounts []*TwitterAccount) *TwitterAccountManager {
return &TwitterAccountManager{
accounts: accounts,
index: 0,
}
}

func (manager *TwitterAccountManager) GetNextAccount() *TwitterAccount {
manager.mutex.Lock()
defer manager.mutex.Unlock()
for i := 0; i < len(manager.accounts); i++ {
account := manager.accounts[manager.index]
manager.index = (manager.index + 1) % len(manager.accounts)
if time.Now().After(account.RateLimitedUntil) {
return account
}
}
return nil
}

func (manager *TwitterAccountManager) MarkAccountRateLimited(account *TwitterAccount) {
manager.mutex.Lock()
defer manager.mutex.Unlock()
account.RateLimitedUntil = time.Now().Add(time.Hour)
}

func Auth(account *TwitterAccount) *twitterscraper.Scraper {
scraper := twitterscraper.New()
baseDir := config.GetInstance().MasaDir

if err := LoadCookies(scraper, account, baseDir); err == nil {
logrus.Debugf("Cookies loaded for user %s.", account.Username)
if IsLoggedIn(scraper) {
logrus.Debugf("Already logged in as %s.", account.Username)
return scraper
}
}

time.Sleep(100 * time.Millisecond)

var err error
if account.TwoFACode != "" {
err = Login(scraper, account.Username, account.Password, account.TwoFACode)
} else {
err = Login(scraper, account.Username, account.Password)
}

if err != nil {
logrus.WithError(err).Warnf("Login failed for %s", account.Username)
return nil
}

time.Sleep(100 * time.Millisecond)

if err = SaveCookies(scraper, account, baseDir); err != nil {
logrus.WithError(err).Errorf("Failed to save cookies for %s", account.Username)
}

logrus.Debugf("Login successful for %s", account.Username)
return scraper
}

// Login attempts to log in to the Twitter scraper service.
// It supports three modes of operation:
// 1. Basic login using just a username and password.
// 2. Login requiring an email confirmation, using a username, password, and email address.
// 3. Login with two-factor authentication, using a username, password, and 2FA code.
// Parameters:
// - scraper: A pointer to an instance of the twitterscraper.Scraper.
// - credentials: A variadic list of strings representing login credentials.
// The function expects either two strings (username, password) for basic login,
// or three strings (username, password, email/2FA code) for email confirmation or 2FA.
//
// Returns an error if login fails or if an invalid number of credentials is provided.
func Login(scraper *twitterscraper.Scraper, credentials ...string) error {
var err error
switch len(credentials) {
case 2:
// Basic login with username and password.
err = scraper.Login(credentials[0], credentials[1])
case 3:
// The third parameter is used for either email confirmation or a 2FA code.
// This design assumes the Twitter scraper's Login method can contextually handle both cases.
err = scraper.Login(credentials[0], credentials[1], credentials[2])
default:
return fmt.Errorf("invalid number of credentials")
// Return an error if the number of provided credentials is neither 2 nor 3.
return fmt.Errorf("invalid number of login credentials provided")
}
if err != nil {
return fmt.Errorf("%v", err)
Expand All @@ -106,8 +43,9 @@ func IsLoggedIn(scraper *twitterscraper.Scraper) bool {
}

func Logout(scraper *twitterscraper.Scraper) error {
if err := scraper.Logout(); err != nil {
return fmt.Errorf("logout failed: %v", err)
err := scraper.Logout()
if err != nil {
return fmt.Errorf("[-] Logout failed: %v", err)
}
return nil
}
27 changes: 16 additions & 11 deletions pkg/scrapers/twitter/cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,37 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"

twitterscraper "github.com/masa-finance/masa-twitter-scraper"
)

func SaveCookies(scraper *twitterscraper.Scraper, account *TwitterAccount, baseDir string) error {
cookieFile := filepath.Join(baseDir, fmt.Sprintf("%s_twitter_cookies.json", account.Username))
func SaveCookies(scraper *twitterscraper.Scraper, filePath string) error {
cookies := scraper.GetCookies()
data, err := json.Marshal(cookies)
js, err := json.Marshal(cookies)
if err != nil {
return fmt.Errorf("error marshaling cookies: %v", err)
}
if err = os.WriteFile(cookieFile, data, 0644); err != nil {
return fmt.Errorf("error saving cookies: %v", err)
err = os.WriteFile(filePath, js, 0644)
if err != nil {
return fmt.Errorf("error saving cookies to file: %v", err)
}

// Load the saved cookies back into the scraper
if err := LoadCookies(scraper, filePath); err != nil {
return fmt.Errorf("error loading saved cookies: %v", err)
}

return nil
}

func LoadCookies(scraper *twitterscraper.Scraper, account *TwitterAccount, baseDir string) error {
cookieFile := filepath.Join(baseDir, fmt.Sprintf("%s_twitter_cookies.json", account.Username))
data, err := os.ReadFile(cookieFile)
func LoadCookies(scraper *twitterscraper.Scraper, filePath string) error {
js, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("error reading cookies: %v", err)
return fmt.Errorf("error reading cookies from file: %v", err)
}
var cookies []*http.Cookie
if err = json.Unmarshal(data, &cookies); err != nil {
err = json.Unmarshal(js, &cookies)
if err != nil {
return fmt.Errorf("error unmarshaling cookies: %v", err)
}
scraper.SetCookies(cookies)
Expand Down
55 changes: 21 additions & 34 deletions pkg/scrapers/twitter/followers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,36 @@ package twitter
import (
"encoding/json"
"fmt"
"strings"

_ "github.com/lib/pq"
twitterscraper "github.com/masa-finance/masa-twitter-scraper"
"github.com/sirupsen/logrus"
)

// ScrapeFollowersForProfile scrapes the followers of a specific Twitter user.
// It takes the username and count as parameters and returns the scraped followers information and an error if any.
// ScrapeFollowersForProfile scrapes the profile and tweets of a specific Twitter user.
// It takes the username as a parameter and returns the scraped profile information and an error if any.
func ScrapeFollowersForProfile(username string, count int) ([]twitterscraper.Legacy, error) {
once.Do(initializeAccountManager)
scraper := Auth()

for {
account := accountManager.GetNextAccount()
if account == nil {
return nil, fmt.Errorf("all accounts are rate-limited")
}

scraper := Auth(account)
if scraper == nil {
logrus.Errorf("Authentication failed for %s", account.Username)
continue
}

followingResponse, errString, _ := scraper.FetchFollowers(username, count, "")
if errString != "" {
if strings.Contains(errString, "Rate limit exceeded") {
accountManager.MarkAccountRateLimited(account)
logrus.Warnf("Rate limited: %s", account.Username)
continue
}
logrus.Errorf("Error fetching followers: %v", errString)
return nil, fmt.Errorf("%v", errString)
}
if scraper == nil {
return nil, fmt.Errorf("there was an error authenticating with your Twitter credentials")
}

// Marshal the followingResponse into a JSON string for logging
responseJSON, err := json.Marshal(followingResponse)
if err != nil {
logrus.Errorf("Error marshaling followingResponse: %v", err)
} else {
logrus.Debugf("Following response: %s", responseJSON)
}
followingResponse, errString, _ := scraper.FetchFollowers(username, count, "")
if errString != "" {
logrus.Printf("Error fetching profile: %v", errString)
return nil, fmt.Errorf("%v", errString)
}

return followingResponse, nil
// Marshal the followingResponse into a JSON string for logging
responseJSON, err := json.Marshal(followingResponse)
if err != nil {
// Log the error if the marshaling fails
logrus.Errorf("[-] Error marshaling followingResponse: %v", err)
} else {
// Log the JSON string of followingResponse
logrus.Debugf("Following response: %s", responseJSON)
}

return followingResponse, nil
}
Loading

0 comments on commit 33b942a

Please sign in to comment.