reverse_engineered_twitter_api/
auth.rs1use super::{ReAPI, BEARER_TOKEN, GUEST_ACTIVE_URL, LOGIN_URL, VERIFY_CREDENTIALS_URL};
2use reqwest::{self, Error};
3use serde::Deserialize;
4use serde_json::{self, json};
5
6#[derive(Deserialize)]
7pub struct User {
8 pub id: i64,
9 pub id_str: String,
10 pub name: String,
11 pub screen_name: String,
12}
13#[derive(Deserialize)]
14pub struct OpenAccount {
15 pub user: Option<User>,
16 pub next_link: Option<Link>,
17 pub attribution_event: Option<String>,
18}
19
20#[derive(Deserialize)]
21pub struct Subtask {
22 pub subtask_id: String,
23 pub open_account: Option<OpenAccount>,
24}
25
26#[derive(Deserialize)]
27pub struct ApiError {
28 pub code: i64,
29 pub message: String,
30}
31
32#[derive(Deserialize)]
33pub struct Flow {
34 pub errors: Option<Vec<ApiError>>,
35 pub flow_token: String,
36 pub status: String,
37 pub subtasks: Vec<Subtask>,
38 pub js_instrumentation: Option<Insrumentation>,
39}
40
41#[derive(Deserialize)]
42pub struct Insrumentation {
43 pub url: String,
44 pub timeout_ms: i64,
45 pub next_link: Link,
46}
47
48#[derive(Deserialize)]
49pub struct Link {
50 pub link_type: String,
51 pub link_id: String,
52}
53
54#[derive(Deserialize)]
55pub struct GuestToken {
56 pub guest_token: String,
57}
58
59#[derive(Deserialize)]
60pub struct VerifyCredentials {
61 pub errors: Option<Vec<ApiError>>,
62}
63
64impl ReAPI {
65 pub fn new() -> ReAPI {
66 let client = reqwest::ClientBuilder::new()
67 .cookie_store(true)
68 .build()
69 .unwrap();
70 return ReAPI {
71 client,
72 csrf_token: String::from(""),
73 guest_token: String::from(""),
74 };
75 }
76 async fn get_flow(&mut self, body: serde_json::Value) -> Result<Flow, Error> {
77 if self.guest_token.is_empty() {
78 self.get_guest_token().await?
79 }
80 let res = self
81 .client
82 .post(LOGIN_URL)
83 .header("Authorization", format!("Bearer {}", BEARER_TOKEN))
84 .header("Content-Type", "application/json")
85 .header("User-Agent", "TwitterAndroid/99")
86 .header("X-Guest-Token", self.guest_token.replace("\"", ""))
87 .header("X-Twitter-Auth-Type", "OAuth2Client")
88 .header("X-Twitter-Active-User", "yes")
89 .header("X-Twitter-Client-Language", "en")
90 .json(&body)
91 .send()
92 .await?;
93
94 let cookies = res.cookies();
95 for cookie in cookies {
96 if cookie.name().eq("ct0") {
97 self.csrf_token = cookie.value().to_string()
98 }
99 }
100 let result: Flow = res.json().await?;
101 return Ok(result);
102 }
103
104 async fn get_flow_token(&mut self, data: serde_json::Value) -> Result<String, String> {
105 let res = self.get_flow(data);
106 match res.await {
107 Ok(info) => {
108 if info.subtasks.len() > 0 {
109 let subtask_id = info.subtasks[0].subtask_id.as_str();
110 match subtask_id {
111 "LoginEnterAlternateIdentifierSubtask"
112 | "LoginAcid"
113 | "LoginTwoFactorAuthChallenge"
114 | "DenyLoginSubtask" => {
115 return Err(format!("Auth error: {}", subtask_id));
116 }
117 _ => return Ok(info.flow_token),
118 }
119 }
120 return Ok(info.flow_token);
121 }
122 Err(e) => Err(format!("Request error: {}", e.to_string())),
123 }
124 }
125
126 async fn get_guest_token(&mut self) -> Result<(), Error> {
127 let token = format!("Bearer {}", BEARER_TOKEN);
128 let res = self
129 .client
130 .post(GUEST_ACTIVE_URL)
131 .header("Authorization", token)
132 .send()
133 .await;
134 match res {
135 Ok(r) => {
136 let op = r.json::<serde_json::Value>().await?;
137 let guest_token = op.get("guest_token").unwrap();
138 self.guest_token = guest_token.to_string();
139 return Ok(());
140 }
141 Err(e) => Err(e),
142 }
143 }
144
145 pub async fn login(
146 &mut self,
147 user_name: &str,
148 password: &str,
149 confirmation: &str,
150 ) -> Result<String, String> {
151 let data = json!(
153 {
154 "flow_name": "login",
155 "input_flow_data": {
156 "flow_context" : {
157 "debug_overrides": {},
158 "start_location": {
159 "location": "splash_screen"
160 }
161 }
162 }
163 }
164 );
165 let flow_token = self.get_flow_token(data).await?;
166
167 let data = json!(
169 {
170 "flow_token": flow_token,
171 "subtask_inputs" : [{
172 "subtask_id": "LoginJsInstrumentationSubtask",
173 "js_instrumentation":{
174 "response": "{}",
175 "link": "next_link"
176 }
177 }],
178 }
179 );
180 let flow_token = self.get_flow_token(data).await?;
181
182 let data = json!(
184 {
185 "flow_token": flow_token,
186 "subtask_inputs" : [{
187 "subtask_id": "LoginEnterUserIdentifierSSO",
188 "settings_list": {
189 "setting_responses" : [{
190 "key": "user_identifier",
191 "response_data": {
192 "text_data" :{
193 "result": user_name
194 }
195 }
196 }],
197 "link": "next_link"
198 }
199 }]
200 }
201 );
202 let flow_token = self.get_flow_token(data).await?;
203
204 let data = json!(
206 {
207 "flow_token": flow_token,
208 "subtask_inputs": [{
209 "subtask_id": "LoginEnterPassword",
210 "enter_password": {
211 "password": password,
212 "link": "next_link"
213 },
214 }]
215 }
216 );
217 let flow_token = self.get_flow_token(data).await?;
218
219 let data = json!(
221 {
222 "flow_token": flow_token,
223 "subtask_inputs": [{
224 "subtask_id": "AccountDuplicationCheck",
225 "check_logged_in_account": {
226 "link": "AccountDuplicationCheck_false"
227 },
228 }]
229 }
230 );
231 let flow_token = self.get_flow_token(data).await;
232
233 match flow_token {
234 Err(e) => {
235 let mut confirmation_subtask = "";
236 for item in vec!["LoginAcid", "LoginTwoFactorAuthChallenge"] {
237 if e.contains(item) {
238 confirmation_subtask = item;
239 break;
240 }
241 }
242 if !confirmation_subtask.is_empty() {
243 if confirmation.is_empty() {
244 let msg = format!(
245 "confirmation data required for {}",
246 confirmation_subtask.to_owned()
247 );
248 return Err(msg);
249 }
250 let data = json!(
251 {
252 "flow_token": "",
253 "subtask_inputs": {
254 "subtask_id": confirmation_subtask,
255 "enter_text": {
256 "text": confirmation,
257 "link": "next_link",
258 },
259 },
260 }
261 );
262 return self.get_flow_token(data).await;
263 }
264 Ok("".to_owned())
265 }
266 Ok(_) => return Ok("".to_owned()),
267 }
268 }
269
270 pub async fn is_logged_in(&mut self) -> bool {
271 let req = self
272 .client
273 .get(VERIFY_CREDENTIALS_URL)
274 .header("Authorization", format!("Bearer {}", BEARER_TOKEN))
275 .header("X-CSRF-Token", self.csrf_token.to_owned())
276 .build()
277 .unwrap();
278 let res = self.client.execute(req).await.unwrap();
279 let cookies = res.cookies();
280 for cookie in cookies {
281 if cookie.name().eq("ct0") {
282 self.csrf_token = cookie.value().to_string()
283 }
284 }
285 let text = res.text().await.unwrap();
286 let res: VerifyCredentials = serde_json::from_str(&text).unwrap();
287 res.errors.is_none()
288 }
289}