2024-03-02 15:43:02 -08:00
package scraper
import (
"encoding/json"
"fmt"
"strings"
2025-02-14 15:54:36 -08:00
. "gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence"
2024-03-02 15:43:02 -08:00
)
const LOGIN_URL = "https://twitter.com/i/api/1.1/onboarding/task.json"
type flow_result struct {
FlowToken string ` json:"flow_token" `
Subtasks [ ] struct {
SubtaskID string ` json:"subtask_id" `
// Login success
OpenAccount struct {
User struct {
ID int ` json:"id_str,string" `
Name string
ScreenName string ` json:"screen_name" `
}
} ` json:"open_account" `
// Phone verification challenge
EnterText struct {
PrimaryText struct {
Text string ` json:"text" `
} ` json:"primary_text" `
SecondaryText struct {
Text string ` json:"text" `
} ` json:"secondary_text" `
} ` json:"enter_text" `
} ` json:"subtasks" `
}
type ChallengeResult struct {
FlowToken string
SubtaskID string
Data map [ string ] string
PrimaryText string
SecondaryText string
}
func ( api * API ) do_login_task ( flow_token string , task_id string , data map [ string ] string ) ( result flow_result ) {
var body string
switch task_id {
// Regular login flow
case "LoginJsInstrumentationSubtask" :
body = fmt . Sprintf (
` { "flow_token":"%s", "subtask_inputs": [ { "subtask_id": "LoginJsInstrumentationSubtask", "js_instrumentation": { "response": " { \"rf\": { \"a560cdc18ff70ce7662311eac0f2441dd3d3ed27c354f082f587e7a30d1a7d5f\":72,\"a8e890b5fec154e7af62d8f529fbec5942dfdd7ad41597245b71a3fbdc9a180d\":176,\"a3c24597ad4c862773c74b9194e675e96a7607708f6bbd0babcfdf8b109ed86d\":-161,\"af9847e2cd4e9a0ca23853da4b46bf00a2e801f98dc819ee0dd6ecc1032273fa\":-8},\"s\":\"hOai7h2KQi4RBGKSYLUhH0Y0fBm5KHIJgxD5AmNKtwP7N8gpVuAqP8o9n2FpCnNeR1d6XbB0QWkGAHiXkKao5PhaeXEZgPJU1neLcVgTnGuFzpjDnGutCUgYaxNiwUPfDX0eQkgr_q7GWmbB7yyYPt32dqSd5yt-KCpSt7MOG4aFmGf11xWE4MTpXfkefbnX4CwZeEFKQQYzJptOvmUWa7qI0A69BSOs7HZ_4Wry2TwB9k03Q_S-MDZAZ3yB_L7WoosVVb1e84YWgaLWWzqhz4C77jDy6isT8EKSWKWnVctsIcaqM_wMV8AiYa5lr0_WkN5TwK9h0vDOTS1obOZuhAAAAYTZan_3\"}", "link": "next_link"}}]} ` , //nolint:lll // json body
flow_token )
case "LoginEnterUserIdentifierSSO" :
username_json , is_ok := data [ "username_json" ]
if ! is_ok {
panic ( "No username provided" )
}
body = fmt . Sprintf (
` { "flow_token":"%s","subtask_inputs":[ { "subtask_id":"LoginEnterUserIdentifierSSO","settings_list": { "setting_responses":[ { "key":"user_identifier","response_data": { "text_data": { "result": ` + username_json + ` }}}],"link":"next_link"}}]} ` , //nolint:lll // json body
flow_token )
case "LoginEnterPassword" :
password_json , is_ok := data [ "password_json" ]
if ! is_ok {
panic ( "No password provided" )
}
body = fmt . Sprintf (
` { "flow_token":"%s","subtask_inputs":[ { "subtask_id":"LoginEnterPassword","enter_password": { "password": ` + password_json + ` ,"link":"next_link"}}]} ` , //nolint:lll // json body
flow_token )
case "AccountDuplicationCheck" :
body = fmt . Sprintf (
` { "flow_token":"%s","subtask_inputs":[ { "subtask_id":"AccountDuplicationCheck","check_logged_in_account": { "link":"AccountDuplicationCheck_false"}}]} ` , //nolint:lll // json body
flow_token )
// Challenge flows
case "LoginAcid" :
body = fmt . Sprintf (
` { "flow_token":"%s","subtask_inputs":[ { "subtask_id":"LoginAcid","enter_text": { "text":"%s","link":"next_link"}}]} ` ,
flow_token ,
data [ "phone" ] )
case "LoginEnterAlternateIdentifierSubtask" :
body = fmt . Sprintf (
2024-03-02 15:45:17 -08:00
` { "flow_token":"%s","subtask_inputs":[ { "subtask_id":"LoginEnterAlternateIdentifierSubtask","enter_text": { "text":"%s","link":"next_link"}}]} ` , //nolint:lll // json body
2024-03-02 15:43:02 -08:00
flow_token ,
data [ "phone" ] )
default :
panic ( "Unknown task_id: " + task_id )
}
err := api . do_http_POST ( LOGIN_URL , body , & result )
if err != nil {
fmt . Printf ( "api.Client.Jar: %#v\n" , api . Client . Jar )
panic ( err )
}
return
}
// Conducts the "login flow".
//
// Logging in is implemented as a series of subtasks in which username and password are submitted in
// separate requests ("tasks"). Other possible tasks include answering challenges like "verify your
// phone number", etc.
//
// To log in, we do "flow tasks" in sequence until the result is "LoginSuccessSubtask"
func ( api * API ) LogIn ( username string , password string ) * ChallengeResult {
// Format username and password safely as JSON (escape quotes, etc)
username_json , err := json . Marshal ( username )
if err != nil {
panic ( err )
}
password_json , err := json . Marshal ( password )
if err != nil {
panic ( err )
}
data := map [ string ] string {
"username_json" : string ( username_json ) ,
"password_json" : string ( password_json ) ,
}
// Begin flow
var result flow_result
err = api . do_http_POST ( LOGIN_URL + "?flow_name=login" , "" , & result )
if err != nil {
panic ( err )
}
// Continue login flow
return api . continue_login_flow ( result , data )
}
// Continue flow until finished or challenged
// This helper function lets you re-enter the login flow after a challenge disrupts it
func ( api * API ) continue_login_flow ( result flow_result , data map [ string ] string ) * ChallengeResult {
for result . Subtasks [ 0 ] . SubtaskID != "LoginSuccessSubtask" {
if result . FlowToken == "" { // Sanity check
panic ( "No flow token." )
}
result = api . do_login_task ( result . FlowToken , result . Subtasks [ 0 ] . SubtaskID , data )
// Check for challenges
if result . Subtasks [ 0 ] . EnterText . PrimaryText . Text != "" {
// Challenge issued
return & ChallengeResult {
FlowToken : result . FlowToken ,
Data : data ,
SubtaskID : result . Subtasks [ 0 ] . SubtaskID ,
PrimaryText : result . Subtasks [ 0 ] . EnterText . PrimaryText . Text ,
SecondaryText : result . Subtasks [ 0 ] . EnterText . SecondaryText . Text ,
}
}
}
// Login successful
api . UserID = UserID ( result . Subtasks [ 0 ] . OpenAccount . User . ID )
api . UserHandle = UserHandle ( result . Subtasks [ 0 ] . OpenAccount . User . ScreenName )
api . update_csrf_token ( )
api . IsAuthenticated = true
return nil
}
func ( api * API ) LoginVerifyPhone ( challenge ChallengeResult , phone_num string ) {
data := challenge . Data
data [ "phone" ] = strings . TrimSpace ( phone_num )
result := api . do_login_task ( challenge . FlowToken , challenge . SubtaskID , data )
api . continue_login_flow ( result , data )
}