This commit is contained in:
Brandon Thomas committed 2025-10-17 23:50:03 -04:00
1 parent 79f5bfdf26
commit 253e5e4287
11 files changed
+322 -84

No files matched your search

@@ -0,0 +1,10 @@
/// Firefox 139 (2025) on macOS, which matches wreq's Emulation::Firefox139
pub const FIREFOX_139_MAC_USER_AGENT : &str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0";
pub const FIREFOX_143_MAC_USER_AGENT : &str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0";
pub const FIREFOX_144_MAC_USER_AGENT : &str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0";
@@ -0,0 +1 @@
pub mod browser_user_agents;
@@ -3,6 +3,9 @@
#[cfg(test)]
pub (crate) mod test_utils;
// Library utils
pub (crate) mod client;
// User lib
pub mod credentials;
pub mod error;
@@ -1,17 +1,18 @@
use crate::client::browser_user_agents::FIREFOX_139_MAC_USER_AGENT;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use std::ops::Deref;
use std::time::Duration;
use tokio::io::AsyncReadExt;
use wreq::Client;
use wreq::header::USER_AGENT;
use wreq::Client;
use wreq_util::Emulation;
const WEBSOCKET_URL: &str = "wss://grok.com/ws/imagine/listen";
pub async fn create_listen_websocket_new() -> Result<(), GrokError> {
let client = Client::builder()
.emulation(Emulation::Chrome140)
.emulation(Emulation::Firefox139)
.connection_verbose(true)
.connect_timeout(Duration::from_secs(10))
.build()
@@ -21,14 +22,13 @@ pub async fn create_listen_websocket_new() -> Result<(), GrokError> {
//.cookie_store(true)
let cookies = "_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=e7.LXFbmc.U81rZIqxuBfBe88yuBZMXEP.zwxNkbOxw-1760724394-1.2.1.1-JMHrapBGxcZriUw852.NCqwFRpZJFvoYxq.mV0jDpKCkiHBZwpdv09XJi6eFLJUYJJ6UDZ3c1eAsucVYLdWN.SvA9M6qzujj8nY4ym03PxQlMEd2OmXJogtDDJbPhA5AZEGxA39_6QQlvaBIUBnYPALYrTl9XJN_V4q3n4BXtpoBzrIJURMIn0mW3esUCGv0NukuVQrkrNtMgqT5SUmTGi0idaBYoR_2_wv4P09lsug; _ga_8FEWB057YH=GS2.1.s1760724394$o3$g1$t1760724401$j53$l0$h0";
let user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36";
//let user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36";
let builder = client.websocket(WEBSOCKET_URL)
.header("Accept", "*/*")
.header("Cookie", cookies)
.header("Host", "grok.com")
.header("Host", "grok.com")
.header(USER_AGENT, user_agent);
.header(USER_AGENT, FIREFOX_139_MAC_USER_AGENT);
let response = builder.send()
.await
@@ -0,0 +1,103 @@
{
"ja3": "772,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,65037-23-13-11-17613-16-5-45-51-0-35-65281-18-27-43-10,4588-29-23-24,0",
"ja3n": "772,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-23-27-35-43-45-51-17613-65037-65281,4588-29-23-24,0",
"ja3_digest": "d32a22eaeec9c58fb7db3d14f344aad0",
"ja3n_digest": "dcefaf3f0e71d260d19dc1d0749c9278",
"scrapfly_fp": "version:772|ch_ciphers:GREASE-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53|ch_extensions:GREASE-0-5-10-11-13-16-18-23-27-35-43-45-51-17613-65037-65281-GREASE|groups:GREASE-4588-29-23-24|points:0|compression:0|supported_versions:GREASE-772-771|supported_protocols:h2-http11|key_shares:GREASE-4588-29|psk:1|signature_algs:1027-2052-1025-1283-2053-1281-2054-1537|early_data:0|",
"scrapfly_fp_digest": "2084695c9595178c76ba04d5d080dab8",
"tls": {
"version": "0x0303 - TLS 1.2",
"ciphers": [
"0x9A9A",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA"
],
"curves": [
"TLS_GREASE (0xAAAA)",
"X25519MLKEM768 (4588)",
"X25519 (29)",
"secp256r1 (23)",
"secp384r1 (24)"
],
"extensions": [
"GREASE (0xCACA)",
"extensionEncryptedClientHello (65037) (boringssl)",
"extended_master_secret (23) (IANA)",
"signature_algorithms (13) (IANA)",
"ec_point_formats (11) (IANA)",
"extensionApplicationSettingsNewCodepoint (17613) (boringssl)",
"application_layer_protocol_negotiation (16) (IANA)",
"status_request (5) (IANA)",
"psk_key_exchange_modes (45) (IANA)",
"key_share (51) (IANA)",
"server_name (0) (IANA)",
"session_ticket (35) (IANA)",
"extensionRenegotiationInfo (boringssl) (65281) (IANA)",
"signed_certificate_timestamp (18) (IANA)",
"compress_certificate (27) (IANA)",
"supported_versions (43) (IANA)",
"supported_groups (10) (IANA)",
"GREASE (0xBABA)"
],
"points": [
"0x00"
],
"protocols": [
"h2",
"http/1.1"
],
"versions": [
"6682",
"772",
"771"
],
"handshake_duration": "80.965498ms",
"is_session_resumption": false,
"session_ticket_supported": true,
"support_secure_renegotiation": true,
"supported_tls_versions": [
6682,
772,
771
],
"supported_protocols": [
"h2",
"http11"
],
"signature_algorithms": [
1027,
2052,
1025,
1283,
2053,
1281,
2054,
1537
],
"psk_key_exchange_mode": "AQ==",
"cert_compression_algorithms": "AA==",
"early_data": false,
"using_psk": false,
"selected_protocol": "h2",
"selected_curve_group": 4588,
"selected_cipher_suite": 4865,
"key_shares": [
43690,
4588,
29
]
}
}
@@ -0,0 +1,106 @@
{
"ja3": "772,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-18-51-43-13-45-28-27-65037,4588-29-23-24-25-256-257,0",
"ja3n": "772,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-5-10-11-13-16-18-23-27-28-34-35-43-45-51-65037-65281,4588-29-23-24-25-256-257,0",
"ja3_digest": "d87a30a5782a73a83c1544bb06332780",
"ja3n_digest": "28ecc2d2875b345cecbb632b12d8c1e0",
"scrapfly_fp": "version:772|ch_ciphers:4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53|ch_extensions:0-5-10-11-13-16-18-23-27-28-34-35-43-45-51-65037-65281|groups:4588-29-23-24-25-256-257|points:0|compression:0|supported_versions:772-771|supported_protocols:h2-http11|key_shares:4588-29-23|psk:1|signature_algs:1027-1283-1539-2052-2053-2054-1025-1281-1537-515-513|early_data:0|",
"scrapfly_fp_digest": "5e7810e397137d931ef22c881d5c0808",
"tls": {
"version": "0x0303 - TLS 1.2",
"ciphers": [
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA"
],
"curves": [
"X25519MLKEM768 (4588)",
"X25519 (29)",
"secp256r1 (23)",
"secp384r1 (24)",
"secp521r1 (25)",
"ffdhe2048 (256)",
"ffdhe3072 (257)"
],
"extensions": [
"server_name (0) (IANA)",
"extended_master_secret (23) (IANA)",
"extensionRenegotiationInfo (boringssl) (65281) (IANA)",
"supported_groups (10) (IANA)",
"ec_point_formats (11) (IANA)",
"session_ticket (35) (IANA)",
"application_layer_protocol_negotiation (16) (IANA)",
"status_request (5) (IANA)",
"delegated_credentials (34) (IANA)",
"signed_certificate_timestamp (18) (IANA)",
"key_share (51) (IANA)",
"supported_versions (43) (IANA)",
"signature_algorithms (13) (IANA)",
"psk_key_exchange_modes (45) (IANA)",
"record_size_limit (28) (IANA)",
"compress_certificate (27) (IANA)",
"extensionEncryptedClientHello (65037) (boringssl)"
],
"points": [
"0x00"
],
"protocols": [
"h2",
"http/1.1"
],
"versions": [
"772",
"771"
],
"handshake_duration": "83.097037ms",
"is_session_resumption": false,
"session_ticket_supported": true,
"support_secure_renegotiation": true,
"supported_tls_versions": [
772,
771
],
"supported_protocols": [
"h2",
"http11"
],
"signature_algorithms": [
1027,
1283,
1539,
2052,
2053,
2054,
1025,
1281,
1537,
515,
513
],
"psk_key_exchange_mode": "AQ==",
"cert_compression_algorithms": "AA==",
"early_data": false,
"using_psk": false,
"selected_protocol": "h2",
"selected_curve_group": 4588,
"selected_cipher_suite": 4865,
"key_shares": [
4588,
29,
23
]
}
}
@@ -1,3 +1,4 @@
mod create_listen_websocket;
mod create_listen_websocket_new;
mod create_listen_websocket_raw_non_websocket;
mod tls_check;
@@ -0,0 +1 @@
mod tls_fingerprint_checking;
@@ -0,0 +1,58 @@
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use std::time::Duration;
use wreq::header::USER_AGENT;
use wreq::Client;
use wreq_util::Emulation;
const FINGERPRINT_URL : &str = "https://tools.scrapfly.io/api/tls";
fn build_chrome_140_client() -> Result<Client, GrokError> {
Ok(Client::builder()
.emulation(Emulation::Chrome140)
.connection_verbose(true)
.connect_timeout(Duration::from_secs(10))
.build()
.map_err(|err| GrokClientError::WreqClientError(err))?)
}
fn build_firefox_139_client() -> Result<Client, GrokError> {
Ok(Client::builder()
.emulation(Emulation::Firefox139) // NB: THIS IS IDENTICAL IN FINGERPRINT TO REAL FIREFOX !!!
.connection_verbose(true)
.connect_timeout(Duration::from_secs(10))
.build()
.map_err(|err| GrokClientError::WreqClientError(err))?)
}
async fn get_with_client(client: &Client) -> Result<String, GrokError> {
let builder = client.get(FINGERPRINT_URL)
.header("Accept", "application/json");
let response = builder.send()
.await
.map_err(|err| GrokClientError::WreqClientError(err))?;
let body = response.text()
.await
.map_err(|err| GrokClientError::WreqClientError(err))?;
Ok(body)
}
#[cfg(test)]
mod tests {
use errors::AnyhowResult;
use super::*;
#[tokio::test]
#[ignore] // manually test
async fn test_chrome_140() -> AnyhowResult<()> {
let client = build_chrome_140_client()?;
let body = get_with_client(&client).await?;
println!("{}", body);
assert_eq!(1, 2);
Ok(())
}
}
@@ -1,3 +1,4 @@
use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::error::grok_generic_api_error::GrokGenericApiError;
@@ -11,12 +12,7 @@ use wreq_util::Emulation;
// Not even sure what this endpoint just, just testing auth
const INDEX_URL: &str = "https://grok.com";
pub struct GetIndexArgs<'a> {
pub cookies: &'a str,
}
pub async fn get_index(args: GetIndexArgs<'_>) -> Result<(), GrokError> {
println!("Building client...");
pub async fn get_index() -> Result<(), GrokError> {
info!("Building client...");
let client = Client::builder()
@@ -24,15 +20,12 @@ pub async fn get_index(args: GetIndexArgs<'_>) -> Result<(), GrokError> {
.build()
.map_err(|err| GrokClientError::WreqClientError(err))?;
println!("Configuring client...");
info!("Configuring client...");
let cookies = args.cookies.to_string();
println!("Cookies: {}", cookies);
let cookie = "stblid=f27fe045-ece1-4ebe-ad6f-ff6a79665416";
let builder = client.get(INDEX_URL)
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0")
.header("User-Agent", FIREFOX_143_MAC_USER_AGENT)
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.header("Accept-Encoding", "gzip, deflate, br, zstd")
.header("Accept-Language", "en-US,en;q=0.5")
@@ -45,21 +38,9 @@ pub async fn get_index(args: GetIndexArgs<'_>) -> Result<(), GrokError> {
.header("priority", "u=0, i")
.header("Pragma", "no-cache")
.header("Cache-Control", "no-cache")
.header("Cookie", cookie)
.header("TE", "trailers");
//.header("Sec-WebSocket-Version", "13")
//.header("Cookie", cookies)
//.header("Sec-WebSocket-Extensions", "permessage-deflate")
//.header(CONNECTION, "keep-alive, Upgrade")
//.header("Sec-WebSocket-Key", "BhBXbFSG6/1xcZVq4ySxcg==") // TODO
//.header(ORIGIN, "https://grok.com")
//.header("Referer", "https://grok.com/imagine")
//.header("Content-Type", "application/json")
// TODO: sentry-trace
// TODO: baggage
//.header("sentry-trace", "235eb6899dcb507c7993058e7055bf28-b37fd92f38d23c3f-0")
//.header("baggage", "sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=235eb6899dcb507c7993058e7055bf28,sentry-org_id=4508179396558848,sentry-sampled=false,sentry-sample_rand=0.5123249939079335,sentry-sample_rate=0")
println!("Sending...");
info!("Sending...");
let response = builder.send()
@@ -69,18 +50,18 @@ pub async fn get_index(args: GetIndexArgs<'_>) -> Result<(), GrokError> {
let status = response.status();
println!("Status: {}", status);
let body = response.text()
.await
.map_err(|err| GrokGenericApiError::WreqError(err))?;
//let body = response.text()
// .await
// .map_err(|err| GrokGenericApiError::WreqError(err))?;
println!("Body: {}", body);
// stblid=08552693-0377-49d3-b17f-8e4c68b153ec
for cookie in response.cookies() {
println!("Cookie: {}={}", cookie.name(), cookie.value());
}
//for (k, v) in response.headers().iter() {
// println!("Header: {}: {:?}", k, v);
//}
println!("Into websocket...");
info!("Into websocket...");
for (name, value) in response.headers() {
println!("Header: {}: {}", name.as_str(), value.to_str().unwrap());
}
Ok(())
}
@@ -88,29 +69,16 @@ pub async fn get_index(args: GetIndexArgs<'_>) -> Result<(), GrokError> {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::get_test_cookies::get_test_cookies;
use crate::test_utils::setup_test_logging::setup_test_logging;
use errors::AnyhowResult;
use log::LevelFilter;
#[tokio::test]
#[ignore] // manually test
async fn create() -> AnyhowResult<()> {
let cookies = get_test_cookies()?;
let args = GetIndexArgs {
cookies: &cookies,
};
let result = get_index(args).await;
match result {
Ok(ok) => {
println!("Okay");
}
Err(err) => {
println!("{:?}", err);
}
}
setup_test_logging(LevelFilter::Trace);
let _result = get_index().await;
assert_eq!(1, 2);
Ok(())
}
}
@@ -7,6 +7,7 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONN
use wreq::ws::message::Message;
use wreq::Client;
use wreq_util::Emulation;
use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT;
// Not even sure what this endpoint just, just testing auth
const TASKS_URL: &str = "https://grok.com/rest/tasks";
@@ -16,36 +17,25 @@ pub struct TasksArgs<'a> {
}
pub async fn get_tasks(args: TasksArgs<'_>) -> Result<(), GrokError> {
println!("Building client...");
info!("Building client...");
//let client = Client::builder()
// .emulation(Emulation::Firefox139)
// .build()
// .map_err(|err| GrokClientError::WreqClientError(err))?;
let client = Client::builder()
.emulation(Emulation::Firefox143)
.build()
.map_err(|err| GrokClientError::WreqClientError(err))?;
println!("Configuring client...");
info!("Configuring client...");
let builder = wreq::get(TASKS_URL)
let builder = client.get(TASKS_URL)
.header(ACCEPT, "*/*")
.header(USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0")
.header(USER_AGENT, FIREFOX_143_MAC_USER_AGENT)
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_ENCODING, "gzip, deflate, br, zstd")
.header(ORIGIN, "https://grok.com")
//.header("Sec-WebSocket-Version", "13")
//.header("Sec-WebSocket-Extensions", "permessage-deflate")
//.header("Sec-WebSocket-Key", "BhBXbFSG6/1xcZVq4ySxcg==") // TODO
//.header("Sec-GPC", "1")
//.header(CONNECTION, "keep-alive, Upgrade")
.header(COOKIE, args.cookies.to_string())
//.header("Sec-Fetch-Dest", "empty")
//.header("Sec-Fetch-Mode", "websocket")
//.header("Sec-Fetch-Site", "same-origin")
.header(PRAGMA, "no-cache")
.header(CACHE_CONTROL, "no-cache");
println!("Sending...");
info!("Sending...");
let response = builder.send()
@@ -55,23 +45,20 @@ pub async fn get_tasks(args: TasksArgs<'_>) -> Result<(), GrokError> {
let status = response.status();
println!("Status: {}", status);
for cookie in response.cookies() {
println!("Cookie: {}={}", cookie.name(), cookie.value());
}
for (name, value) in response.headers() {
println!("Header: {}: {}", name.as_str(), value.to_str().unwrap());
}
let body = response.text()
.await
.map_err(|err| GrokGenericApiError::WreqError(err))?;
println!("Body: {}", body);
//for (k, v) in response.headers().iter() {
// println!("Header: {}: {:?}", k, v);
//}
//println!("Upgrading...");
//info!("Upgrading...");
//let upgraded = response
// .upgrade()
// .await
// .map_err(|err| GrokClientError::WreqClientError(err))?;
Ok(())
}