diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 350b9d4..475af59 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,4 +25,5 @@ jobs: env: TWITTER_USERNAME: ${{ secrets.TWITTER_USERNAME }} TWITTER_PASSWORD: ${{ secrets.TWITTER_PASSWORD }} + TWITTER_EMAIL: ${{ secrets.TWITTER_EMAIL }} run: go test -v diff --git a/README.md b/README.md index 8e5549a..5749d3a 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,13 @@ It is also required to search. err := scraper.Login("username", "password") ``` +Use username to login, not email! +But if you have email confirmation, use email address in addition: + +```golang +err := scraper.Login("username", "password", "email") +``` + Status of login can be checked with: ```golang diff --git a/auth.go b/auth.go index b1ef9ae..cc006d7 100644 --- a/auth.go +++ b/auth.go @@ -22,6 +22,9 @@ type ( } `json:"errors"` FlowToken string `json:"flow_token"` Status string `json:"status"` + Subtasks []struct { + SubtaskID string `json:"subtask_id"` + } `json:"subtasks"` } verifyCredentials struct { @@ -69,7 +72,15 @@ func (s *Scraper) getFlowToken(data map[string]interface{}) (string, error) { return "", fmt.Errorf("auth error (%d): %v", info.Errors[0].Code, info.Errors[0].Message) } - return info.FlowToken, nil + if info.Subtasks != nil && len(info.Subtasks) > 0 { + if info.Subtasks[0].SubtaskID == "LoginEnterAlternateIdentifierSubtask" { + err = fmt.Errorf("auth error: %v", "LoginEnterAlternateIdentifierSubtask") + } else if info.Subtasks[0].SubtaskID == "LoginAcid" { + err = fmt.Errorf("auth error: %v", "LoginAcid") + } + } + + return info.FlowToken, err } // IsLoggedIn check if scraper logged in @@ -92,7 +103,21 @@ func (s *Scraper) IsLoggedIn() bool { } // Login to Twitter -func (s *Scraper) Login(username string, password string) error { +// Use Login(username, password) for ordinary login +// or Login(username, password, email) for login if you have email confirmation +func (s *Scraper) Login(credentials ...string) error { + var username, password, email string + if len(credentials) == 2 { + username = credentials[0] + password = credentials[1] + } else if len(credentials) == 3 { + username = credentials[0] + password = credentials[1] + email = credentials[2] + } else { + return fmt.Errorf("invalid credentials") + } + s.setBearerToken(bearerToken2) err := s.GetGuestToken() @@ -178,9 +203,26 @@ func (s *Scraper) Login(username string, password string) error { }, }, } - _, err = s.getFlowToken(data) + flowToken, err = s.getFlowToken(data) if err != nil { - return err + if strings.Contains(err.Error(), "LoginAcid") { + // flow acid + data = map[string]interface{}{ + "flow_token": flowToken, + "subtask_inputs": []map[string]interface{}{ + { + "subtask_id": "LoginAcid", + "enter_text": map[string]interface{}{"text": email, "link": "next_link"}, + }, + }, + } + _, err = s.getFlowToken(data) + if err != nil { + return err + } + } else { + return err + } } s.isLogged = true diff --git a/auth_test.go b/auth_test.go index 38a7b6b..721751e 100644 --- a/auth_test.go +++ b/auth_test.go @@ -10,15 +10,16 @@ import ( var ( username = os.Getenv("TWITTER_USERNAME") password = os.Getenv("TWITTER_PASSWORD") + email = os.Getenv("TWITTER_EMAIL") ) func TestAuth(t *testing.T) { scraper := twitterscraper.New() - if err := scraper.Login(username, password); err != nil { + if err := scraper.Login(username, password, email); err != nil { t.Fatalf("Login() error = %v", err) } if !scraper.IsLoggedIn() { - t.Error("Expected IsLoggedIn() = true") + t.Fatalf("Expected IsLoggedIn() = true") } cookies := scraper.GetCookies() scraper.Logout() diff --git a/search_test.go b/search_test.go index f3d85cd..12ff7d6 100644 --- a/search_test.go +++ b/search_test.go @@ -13,7 +13,7 @@ func authSearchScraper() error { if searchScraper.IsLoggedIn() { return nil } - return searchScraper.Login(username, password) + return searchScraper.Login(username, password, email) } func TestFetchSearchCursor(t *testing.T) {