commit 7bb560b89b4f711ed45383f225f7c5e3123de8c1 Author: fekhesk Date: Sat Feb 11 08:43:29 2023 -0800 initial stuffz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ac651ab --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "free_twitter_api" +version = "0.1.0" +edition = "2021" + +[dependencies] +isahc = "1.7.2" +rand = "0.8.5" +log = "0.4.17" + +[dev-dependencies] +env_logger = "0.10.0" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0e33b6f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,159 @@ +use std::net::SocketAddr; +use std::str::FromStr; +use std::sync::Arc; +use isahc::auth::{Authentication, Credentials}; +use isahc::{Body, Request, Response}; +use isahc::http::HeaderMap; +use isahc::prelude::*; +use log::debug; + +const USER_AGENTS: [&str; 5] = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.41", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", +]; + +/// this is what we look for to find the bearer token +const SEARCH_STRING: &str = "AAAAAAAAA"; +/// this is what we look for to find the main.*.js file +const MAIN_JS_SEARCH_STRING: &str = "main."; + +struct Proxy { + address: String, + credentials: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ProxyError { + InvalidProxyFormat, +} + +#[derive(Clone, Debug)] +pub enum BearerError { + CouldntGetTwitterDotCom(isahc::Error), + CouldntFindMainJsUrl, + CouldntGetMainDotJs(isahc::Error), + CouldntFindBearerToken, +} + +impl FromStr for Proxy { + type Err = ProxyError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let host = parts.next().ok_or(ProxyError::InvalidProxyFormat)?; + let port = parts.next().ok_or(ProxyError::InvalidProxyFormat)?; + let auth = if let Some(user) = parts.next() { + let pass = parts.next().ok_or(ProxyError::InvalidProxyFormat)?; + Some(Credentials::new(user, pass)) + } else { + None + }; + Ok(Proxy { + address: format!("{}:{}", host, port), + credentials: auth, + }) + } +} + +#[derive(Clone)] +pub struct Client { + http_client: Arc, + proxy: Option>, +} + +impl Default for Client { + fn default() -> Self { + Self::new() + } +} + +impl Client { + pub fn new() -> Self { + Client { + http_client: Arc::new(isahc::HttpClient::new().expect("failed to create http client")), + proxy: None, + } + } + + pub fn new_with_proxy(proxy: &str) -> Self { + let proxy = proxy.parse().unwrap(); + Client { + http_client: Arc::new(isahc::HttpClient::new().expect("failed to create http client")), + proxy: Some(Arc::new(proxy)), + } + } + + fn get(&self, url: &str, headers: &HeaderMap) -> Result, isahc::Error> { + let mut request = Request::get(url); + if let Some(proxy) = &self.proxy { + request = request.proxy(Some(proxy.address.parse().expect("error parsing proxy!"))); + if let Some(creds) = &proxy.credentials { + request = request.proxy_authentication(Authentication::basic()); + request = request.proxy_credentials(creds.clone()); + } + } + let mut request = request.body(())?; + request.headers_mut().extend(headers.iter().map(|(k, v)| (k.clone(), v.clone()))); + self.http_client.send(request) + } + + fn get_twitter_bearer_token(&self) -> Result { + let url = "https://twitter.com"; + let mut headers = HeaderMap::new(); + let user_agent = USER_AGENTS[rand::random::() % USER_AGENTS.len()]; + headers.insert("user-agent", user_agent.parse().unwrap()); + + let mut response = self.get(url, &headers).map_err(BearerError::CouldntGetTwitterDotCom)?; + let body = response.text().unwrap(); + let mut mainjs_url = None; + // fixme! this is really fucking cursed + for line in body.lines() { + if line.contains(MAIN_JS_SEARCH_STRING) { + let main_js_start = line.find(MAIN_JS_SEARCH_STRING).unwrap(); + let main_js_end = line[main_js_start..].find('"').unwrap() + main_js_start; + let main_js_url_start = line[0..main_js_start].rfind('"').unwrap(); + let main_js_url = &line[main_js_url_start + 1..main_js_end]; + mainjs_url = Some(main_js_url.to_string()); + } + } + let mainjs_url = mainjs_url.ok_or(BearerError::CouldntFindMainJsUrl)?; + debug!("mainjs_url: {}", mainjs_url); + // now get the main.js file + let mut response = self.get(&mainjs_url, &headers).map_err(BearerError::CouldntGetMainDotJs)?; + let body = response.text().unwrap(); + let mut token = None; + for line in body.lines() { + if line.contains(SEARCH_STRING) { + let bearer_token_start = line.find(SEARCH_STRING).unwrap(); + let bearer_token_end = line[bearer_token_start..].find('"').unwrap() + bearer_token_start; + let bearer_token = &line[bearer_token_start..bearer_token_end]; + token = Some(bearer_token.to_string()); + } + } + + let token = token.ok_or(BearerError::CouldntFindBearerToken)?; + Ok(token) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn world_normal() { + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn get_bearer_token() { + env_logger::init(); + let client = Client::new(); + let bearer_token = client.get_twitter_bearer_token(); + println!("{:?}", bearer_token); + } +} diff --git a/src/token_generator.rs b/src/token_generator.rs new file mode 100644 index 0000000..e69de29